Android: AudioTrack vs. multithreading - android

I'm facing a problem where I want to play a half second long AudioTrack in static mode repeatedly, but the sound is choppy. However, I noticed that the sound is perfectly clear while a TransitionDrawable is running in parallel.
A simplified skeleton of my code is:
thread = new Thread(new Runnable() {
public void run() {
createTransition();
try {
createAudioTrack();
while (true) {
if (audio) {
playSoundClip();
}
if (display) {
playScreenTransition();
}
Thread.sleep(getDelayBetweenBeats());
}
} catch (InterruptedException e) {
} finally {
resetScreenTransition();
stopSoundClip();
}
}
private void createAudioTrack() {
short[] samples = generateSamples();
track = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLERATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, samples.length * 2, AudioTrack.MODE_STATIC);
if (track.getState() != AudioTrack.STATE_UNINITIALIZED) {
track.write(samples, 0, samples.length);
}
}
private void playSoundClip() {
if (track != null && track.getState() != AudioTrack.STATE_UNINITIALIZED) {
track.stop();
track.reloadStaticData();
track.play();
}
}
private void playScreenTransition() {
view.post(new Runnable() {
public void run() {
view.setBackgroundDrawable(transition);
transition.startTransition(DURATION);
}
});
}
});
thread.start();
As you can see thread is not performed on the UI-thread so I assume that the track is facing multithreading problems. I don't think that the UI-thread that plays the transition consumes the entire CPU since my audio is playing in parallel. It seems as if the activity somehow consumes the CPU and nothing else is executed.
I had tried to use view.post(new Runnable() {...}); in playSoundClip(), too, but that didn't help.
I thought about changing all into an AsyncTask, but IMHO that wouldn't change anything as it would still be a background task. Since I don't need to update an UI-element with the sound and the transition still has to play in parallel I didn't even try that.
A solution would probably be to always have some transition running in parallel (the actual one or a dummy one), but that just sounds bad (pun?) to me.
Does anyone know of another way how I can make track play clear at all times?
EDIT:
After working some more in this issue and extending my program I noticed that I have to use a threaded approach like I lined out above as the logic in there takes some time to complete and I can't do it all on the UI-thread any more. Currently I play a dummy transition while the audio is playing, which still sounds bad to me. Therefore, if you can still contribute some insights into this topic you are welcome to post/answer them here.

You might want to take a look at SoundPool, which would allow you to statically load your short audio sample into memory once and then play it on-demand with much lower latency. The way in which you are using AudioTrack is a good use of replaying the audio without reloading, but it might still be a bit heavy-weight for such a short and often repeated sound byte.
You might also consider not using a background thread at all. It looks from your snippet like you are really just using the Thread as a timer, and you might get better performance out of using a Handler to post your Runnable on a timed interval (which would also allow you to call your audio/transition methods on the main thread) instead.
HTH

I have much larger chunks of audio but I have had luck playing them in a Service that I created.
P.S. Nice Pun

Related

Android with Nexus 6 -- how to avoid decreased OpenSL audio thread priority relating to app focus?

I'm encountering a strange problem when trying to implement low-latency streaming audio playback on a Nexus 6 running Android 6.0.1 using OpenSL ES.
My initial attempt seemed to be suffering from starvation issues, so I added some basic timing benchmarks in the buffer completion callback function. What I've found is that audio plays back fine if I continually tap the screen while my app is open, but if I leave it alone for a few seconds, the callback starts to take much longer. I'm able to reproduce this behavior consistently. A couple of things to note:
"a few seconds" ~= 3-5 seconds, not long enough to trigger a screen change
My application's activity sets FLAG_KEEP_SCREEN_ON, so no screen changes should occur anyway
I have taken no action to try to increase the audio callback thread's priority, since I was under the impression that Android reserves high priority for these threads already
The behavior occurs on my Nexus 6 (Android 6.0.1), but not on a Galaxy S6 I also have available (Android 5.1.1).
The symptoms I'm seeing really seem like the OS kicks down the audio thread priority after a few seconds of non-interaction with the phone. Is this right? Is there any way I can avoid this behavior?
While watching the latest Google I/O 2016 audio presentation, I finally found the cause and the (ugly) solution for this problem.
Just watch the around one minute of this you tube clip (starting at 8m56s):
https://youtu.be/F2ZDp-eNrh4?t=8m56s
It explains why this is happening and how you can get rid of it.
In fact, Android slows the CPU down after a few seconds of touch inactivity to reduce the battery usage. The guy in the video promises a proper solution for this soon, but for now the only way to get rid of it is to send fake touches (that's the official recommendation).
Instrumentation instr = new Instrumentation();
instr.sendKeyDownUpSync(KeyEvent.KEYCODE_BACKSLASH); // or whatever event you prefer
Repeat this with a timer every 1.5 seconds and the problem will vanish.
I know, this is an ugly hack, and it might have ugly side effects which must be handled. But for now, it is simply the only solution.
Update:
Regarding your latest comment ... here's my solution.
I'm using a regular MotionEvent.ACTION_DOWN at a location outside of the screen bounds. Everything else interfered in an unwanted way with the UI. To avoid the SecurityException, initialize the timer in the onStart() handler of the main activity and terminate it in the onStop() handler. There are still situations when the app goes to the background (depending on the CPU load) in which you might run into a SecurityException, therefore you must surround the fake touch call with a try catch block.
Please note, that I'm using my own timer framework, so you have to transform the code to use whatever timer you want to use.
Also, I cannot ensure yet that the code is 100% bulletproof. My apps have that hack applied, but are currently in beta state, therefore I cannot give you any guarantee if this is working correctly on all devices and Android versions.
Timer fakeTouchTimer = null;
Instrumentation instr;
void initFakeTouchTimer()
{
if (this.fakeTouchTimer != null)
{
if (this.instr == null)
{
this.instr = new Instrumentation();
}
this.fakeTouchTimer.restart();
}
else
{
if (this.instr == null)
{
this.instr = new Instrumentation();
}
this.fakeTouchTimer = new Timer(1500, Thread.MIN_PRIORITY, new TimerTask()
{
#Override
public void execute()
{
if (instr != null && fakeTouchTimer != null && hasWindowFocus())
{
try
{
long downTime = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, -100, -100, 0);
instr.sendPointerSync(event);
event.recycle();
}
catch (Exception e)
{
}
}
}
}, true/*isInfinite*/);
}
}
void killFakeTouchTimer()
{
if (this.fakeTouchTimer != null)
{
this.fakeTouchTimer.interupt();
this.fakeTouchTimer = null;
this.instr = null;
}
}
#Override
protected void onStop()
{
killFakeTouchTimer();
super.onStop();
.....
}
#Override
protected void onStart()
{
initFakeTouchTimer();
super.onStart();
.....
}
It is well known that the audio pipeline in Android 6 has been completely rewritten. While this improved latency-related issues in most cases, it is not impossible that it generated a number of undesirable side-effects, as is usually the case with such large-scale changes.
While your issue does not seem to be a common one, there are a few things you might be able to try:
Increase the audio thread priority. The default priority for audio threads in Android is -16, with the maximum being -20, usually only available to system services. While you can't assign this value to you audio thread, you can assign the next best thing: -19 by using the ANDROID_PRIORITY_URGENT_AUDIO flag when setting the thread's priority.
Increase the number of buffers to prevent any kind of jitter or latency (you can even go up to 16). However on some devices the callback to fill a new buffer isn’t always called when it should.
This SO post has several suggestions to improve audio latency on Anrdoid. Of particular interest are points 3, 4 and 5 in the accepted answer.
Check whether the current Android system is low-latency-enabled by querying whether hasSystemFeature(FEATURE_AUDIO_LOW_LATENCY) or hasSystemFeature(FEATURE_AUDIO_PRO).
Additionally, this academic paper discusses strategies for improving audio latency-related issues in Android/OpenSL, including buffer- and callback interval-related approaches.
Force resampling to native device sample rate on Android 6.
Use the device's native sample rate of 48000. For example:
SLDataFormat_PCM dataFormat;
dataFormat.samplesPerSec = 48000;

How to play only some part of an audio file in android?

seekTo of MediaPlayer class helps in fixing the starting point. How to set the end point? There doesn't appear to be any method available in MediaPlayer class. For e.g., I want to play only the part from 3 to 6 seconds of a 10sec total media. I do see a solution in the form of setting up sleep timer and interrupting the MediaPlayer. Any other better solution available?
You could use a sleep timer, but it may be easier to just use a separate thread. This way you don't have to worry as much about what your UI thread is doing, or interrupting it.
Just use getCurrentPosition() in a threaded loop to monitor the playback time. When it gets to the target "end" time, just stop() it. Adding a sleep delay to the thread is a good idea, too. You don't need to check it as fast as the loop can run, maybe 5-10 times per second should be fine.
If you don´t find the answer for that you could try a work around solution using a webview that opens a html5-JS web site.
Here is an uncomplete quick example. (use the jsfiddle code)
http://jsfiddle.net/mNPCP/5/
<audio id="audio2"
preload="auto"
src="http://upload.wikimedia.org/wikipedia/commons/a/a9/Tromboon-sample.ogg" >
<p>Your browser does not support the audio element</p>
</audio>
<script>
myAudio=document.getElementById('audio2');
myAudio.addEventListener('canplaythrough', function() {
this.currentTime = 12;
this.play();
});
</script>
From this Stackoverflow question
This is not the best solution but maybe can help on something..
Good luck!
It might be a little bit late :) but here is how i solve my problem
private void singlePlayAudio(int start, int duration) {
// duration = end_part - start_part;
//in my case i need to pause
//if other players are playing
pauseMainPlayer();
twoMediaPlayersPause();
final boolean isPlaying = singleMediaPlayer.isPlaying();
if (isPlaying) {
singleMediaPlayHandler.removeCallbacks(singleAudioThread);
singleMediaPlayer.pause();
singleMediaPlayer.seekTo(start);
}
singleAudioThread = () -> {
singleMediaPlayer.pause();
singleMediaPlayer.seekTo(start);
autoViewPagerScroller();
};
singleMediaPlayer.seekTo(start);
singleMediaPlayer.start();
singleMediaPlayHandler.postDelayed(singleAudioThread, duration);
}
private final Handler singleMediaPlayHandler = new Handler(Looper.getMainLooper());
private Runnable singleAudioThread;

Detecting buffering error (or timeout) Android MediaPlayer - Use a Timer for timeout?

Apparently no exception is thrown so that I can recognize an error while buffering streaming audio content. For example I've disconnected my router and the app will continue to try to buffer the whole time. When I reconnect then it completes buffering and continues even after being disconnected for over a minute!
So the problem is I can't let my user sit there for that long without considering that a problem. What is the proper method to detect a buffering problem with the Android media player?
I'm thinking about using a Timer for a timeout. I'll start probably with 15 seconds (using a proxy I tested a 5kbps connection, which would be a worst case, was able to start playing in 6-10 seconds, so I think 15 seconds would be a reasonable timeout period). Does this sound like a good plan? If so should I create a new Timer with each buffer attempt or should I keep the same Timer throughout the lifetime of the playback service?
So basically I'm asking two questions:
1) What's the proper way to detect if a buffer is having a problem? Is there a listener I'm overlooking? I've tried MediaPlayer.OnErrorListener of course that doesn't fire in my tests. My conclusion is I have to have a timeout to detect a buffering error.
2) If I'm correct on number one, what is the proper way to use a Timer? Create one with each buffer attempt or reuse the same one? EDIT Also should I restart the (or cancel and create a new) Timer onBufferUpdate? With the onBufferUpdate listener I should know that some data is coming back so should maybe reset the timer with that.
From your question, I understand that the primary objective is to detect a situation if your player is stalled due to buffering and take some actions thereof. To handle this situation, I feel that the following 2 listeners may be helpful to identify the same.
MediaPlayer.onBufferingUpdate would provide the timely progress of the buffering. So, if there are 2 callbacks with same percent value, this could be an indication of potential buffering.
There is another listener MediaPlayer.onInfoListener which has some specific events which could be of interest to you. On this listener, if the what is MEDIA_INFO_BUFFERING_START, this would indicate that the player is pausing the playback for buffering i.e. trigger for your logic. Similarly MEDIA_INFO_BUFFERING_END indicates the restart of the playback after filling the buffers.
You Should see this article. The mediaplayer has a ErrorListener to get any error.
http://developer.android.com/reference/android/media/MediaPlayer.OnErrorListener.html
int count=40;//for 40 seconds to wait for buffering after it will finish the activity
//boolean timeoutflag=false;
timeout = new Handler(Looper.getMainLooper()) {
#Override
public void handleMessage(Message msg) {
System.out.println("value of count="+msg.getData().getLong("count"));
if (msg.getData().getBoolean("valid")) {
if (msg.getData().getLong("count") == 0 && !timeoutflag)
{
if (pDialog != null && pDialog.isShowing())
{
try
{
pDialog.dismiss();
}catch(Exception e)
{
e.printStackTrace();
}
}
Toast.makeText(getApplicationContext(),
"Unable To Load This Video", Toast.LENGTH_LONG).show();
finish();
} else {
}
}
}
};
timeout.postDelayed(null, 0);
new Thread(new Runnable() {
#Override
public void run() {
while (count > 0) {
try {
Thread.sleep(1020);
} catch (Exception e) {
}
Message msg = new Message();
Bundle b = new Bundle();
b.putBoolean("valid", true);
b.putLong("count", --count);
msg.setData(b);
timeout.sendMessage(msg);
}
}
}).start();
// set timeoutflag=true; in setOnPreparedListener of video view
For buffering during preparation, you have to set your own timer which calls player.reset() after some interval. This puts the player back into init state.
For buffering after preparation (during play) you have to monitor getPosition(). If it falls behind some maximum, call reset(). This allows you to set an experience threshold for your playback. Handles not only failed connection, but also choppy connection.
Best solution is to not use MediaPlayer. Use a public VLC derivative instead. MP has too many internalized private design limitations requiring horrible workarounds (eg. CANT add codecs). RTFM gives you false hope in this case.
Unless you are doing a very straight laced android app, don't depend on any android api. Some opensource substitutes are better supported, and for good reason.
(really bandeely olly jolly satisfying editorial rant deleted)

Reliable sequential audio playback on Android

I am writing an android game for teaching kids to count. The instructions are read to the player through sound clips that are put together to form sentences (for instance "Place", "one", "cow", "in the", "barn". This requires a certain amount of reliability when it comes to latency so that the flow of the instructions sounds natural.
Currently I am using MediaPlayer, playing each sound in a OnCompletionListener. Each sound has it's own MediaPlayer that is created and prepared before playback of any sound starts (to reduce latency) - but still I get a significant delay before each sound the first time it is played (the second time it seems some sort of caching has taken place and it works fine).
The sounds are not many and very short and it should probably work better with SoundPool, but SoundPool has no way of knowing when an audio is complete and thus not an option.
Does anyone have any experience with similar problems and a viable solution?
I have used handler with OnCompletionListener and it worked fine for me to give delay between two sounds.
this way,
CommonMethod.player.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
// /will use count and loop as per number of
// repetition chosen.
handler.postDelayed(new Runnable() {
public void run() {
if (counter >= limit) {
CommonMethod.player.stop();
} else {
CommonMethod.player.start();
}
counter++;
}
}, 3000);
}
});

Problem synchronizing sound and display

I have an app that plays an mp3 file and I'm trying to update a custom field in synchrony with certain times we have tabulated for the sound playback (kind of like a karaoke effect). I'm using a Handler to schedule these updates. In my custom field class, I define a Runnable that is supposed to run the update at the right time:
private final Runnable mTrigger = new Runnable() {
#Override
public void run() {
int now = mPlayer.getCurrentPosition();
if (mState == STATE_PLAYING && mUpdateAction != null) {
if (mTriggerTime - now > MAX_PREMATURE_TRIGGER) {
// Sound is lagging too much; reschedule this trigger
mHandler.postDelayed(this, mTriggerTime - now);
} else {
// Run the update
mUpdateAction.run();
}
}
}
};
When I call mPlayer.start() I schedule the first update by calling mHandler.postDelayed(mTrigger, timeToFirstUpdate). Each update action decides what the next update will be and schedules it (by calling mHandler.postDelayed(mTrigger, timeToNextUpdate)). The updates times are typically a few hundred milliseconds apart.
The problem is that, while some updates are happening promptly at the scheduled times, others can be delayed by 200 milliseconds or more, which is quite noticeable to the user. I'm not doing anything in my app between these updates other than playing the sound. (No background worker threads; no other display updates.) The delays appear to be random and vary considerably each time through.
I didn't think that the timing for postDelayed would be this imprecise! I don't know if this is an emulator issue or a problem with my approach. Does sound playback screw up the timing of the UI thread loop? Should I move the timing into a background thread (and is it safe to call mPlayer.getCurrentPosition() from a background thread)? Something else?
After much experimenting, it seems like the problem is the emulator. When I ran everything on a speedier workstation, the problem seems to have gone away.

Categories

Resources