I am building a mobile optimized website and would like to play a sound after a counter is over. Usually, after 2 or more minutes.
The problem is that once the phone's screen is off, the phone disables my js, and prevents the audio from playing.
Potential Solution:
I read this
In the solution #3, u can loop 2 tracks sequentially, and the mobile phone will still play both even when screen is off.
So the trick I found is to use that loop, but have the first sound:
1. Be silence. Yes, complete 5min silence audio
2. have it loop as many times required before enough time has passed for second song to play.
So for instance, in here, silence audio will play twice before the other sound plays.
The trick is to control the max variable to achieved the desired output.
var count = 0;
var max = 2;
document.getElementById('silence_audio').addEventListener('ended', function () {
this.currentTime = 0;
this.pause();
if (count < max) {
document.getElementById('silence_audio').play();
count++;
} else {
document.getElementById('audio_4').play();
}
}, false);
document.getElementById('audio_4').addEventListener('ended', function () {
this.currentTime = 0;
this.pause();
document.getElementById('silence_audio').play();
}, false);
But my question is, are there any other better solutions?
Thank you!
Related
In the application which I want to create, I face some technical obstacles. I have two music tracks in the application. For example, a user imports the music background as a first track. The second path is a voice recorded by the user to the rhythm of the first track played by the speaker device (or headphones). At this moment we face latency. After recording and playing back in the app, the user hears the loss of synchronisation between tracks, which occurs because of the microphone and speaker latencies.
Firstly, I try to detect the delay by filtering the input sound. I use android’s AudioRecord class, and the method read(). This method fills my short array with audio data.
I found that the initial values of this array are zeros so I decided to cut them out before I will start to write them into the output stream.
So I consider those zeros as a „warmup” latency of the microphone. Is this approach correct? This operation gives some results, but it doesn’t resolve the problem, and at this stage, I’m far away from that.
But the worse case is with the delay between starting the speakers and playing the music. This delay I cannot filter or detect. I tried to create some calibration feature which counts the delay. I play a „beep” sound through the speakers, and when I start to play it, I also begin to measure time. Then, I start recording and listen for this sound being detected by the microphone. When I recognise this sound in the app, I stop measuring time. I repeat this process several times, and the final value is the average from those results. That is how I try to measure the latency of the device. Now, when I have this value, I can simply shift the second track backwards to achieve synchronisation of both records (I will lose some initial milliseconds of the recording, but I skip this case, for now, there are some possibilities to fix it).
I thought that this approach would resolve the problem, but it turned out this is not as simple as I thought. I found two issues here:
1. Delay while playing two tracks simultaneously
2. Random in device audio latency.
The first: I play two tracks using AudioTrack class and I run method play() like this:
val firstTrack = //creating a track
val secondTrack = //creating a track
firstTrack.play()
secondTrack.play()
This code causes delays at the stage of playing tracks. Now, I don’t even have to think about latency while recording; I cannot play two tracks simultaneously without delays. I tested this with some external audio file (not recorded in my app) - I’m starting the same audio file using the code above, and I can see a delay. I also tried it with MediaPlayer class, and I have the same results. In this case, I even try to play tracks when callback OnPreparedListener invoke:
val firstTrack = //AudioPlayer
val secondTrack = //AudioPlayer
second.setOnPreparedListener {
first.start()
second.start()
}
And it doesn’t help.
I know that there is one more class provided by Android called SoundPool. According to the documentation, it can be better with playing tracks simultaneously, but I can’t use it because it supports only small audio files and that can't limit me.
How can I resolve this problem? How can I start playing two tracks precisely at the same time?
The second: Audio latency is not deterministic - sometimes it is smaller, and sometimes it’s huge, and it’s out of my hands. So measuring device latency can help but again - it cannot resolve the problem.
To sum up: is there any solution, which can give me exact latency per device (or app session?) or other triggers which detect actual delay, to provide the best synchronisation while playback two tracks at the same time?
Thank you in advance!
Synchronising audio for karaoke apps is tough. The main issue you seem to be facing is variable latency in the output stream.
This is almost certainly caused by "warm up" latency: the time it takes from hitting "play" on your backing track to the first frame of audio data being rendered by the audio device (e.g. headphones). This can have large variance and is difficult to measure.
The first (and easiest) thing to try is to use MODE_STREAM when constructing your AudioTrack and prime it with bufferSizeInBytes of data prior to calling play (more here). This should result in lower, more consistent "warm up" latency.
A better way is to use the Android NDK to have a continuously running audio stream which is just outputting silence until the moment you hit play, then start sending audio frames immediately. The only latency you have here is the continuous output latency.
If you decide to go down this route I recommend taking a look at the Oboe library (full disclosure: I am one of the authors).
To answer one of your specific questions...
Is there a way to calculate the latency of the audio output stream programatically?
Yes. The easiest way to explain this is with a code sample (this is C++ for the AAudio API but the principle is the same using Java AudioTrack):
// Get the index and time that a known audio frame was presented for playing
int64_t existingFrameIndex;
int64_t existingFramePresentationTime;
AAudioStream_getTimestamp(stream, CLOCK_MONOTONIC, &existingFrameIndex, &existingFramePresentationTime);
// Get the write index for the next audio frame
int64_t writeIndex = AAudioStream_getFramesWritten(stream);
// Calculate the number of frames between our known frame and the write index
int64_t frameIndexDelta = writeIndex - existingFrameIndex;
// Calculate the time which the next frame will be presented
int64_t frameTimeDelta = (frameIndexDelta * NANOS_PER_SECOND) / sampleRate_;
int64_t nextFramePresentationTime = existingFramePresentationTime + frameTimeDelta;
// Assume that the next frame will be written into the stream at the current time
int64_t nextFrameWriteTime = get_time_nanoseconds(CLOCK_MONOTONIC);
// Calculate the latency
*latencyMillis = (double) (nextFramePresentationTime - nextFrameWriteTime) / NANOS_PER_MILLISECOND;
A caveat: This method relies on accurate timestamps being reported by the audio hardware. I know this works on Google Pixel devices but have heard reports that it isn't so accurate on other devices so YMMV.
Following the answer of donturner, here's a Java version (that also uses other methods depending on the SDK version)
/** The audio latency has not been estimated yet */
private static long AUDIO_LATENCY_NOT_ESTIMATED = Long.MIN_VALUE+1;
/** The audio latency default value if we cannot estimate it */
private static long DEFAULT_AUDIO_LATENCY = 100L * 1000L * 1000L; // 100ms
/**
* Estimate the audio latency
*
* Not accurate at all, depends on SDK version, etc. But that's the best
* we can do.
*/
private static void estimateAudioLatency(AudioTrack track, long audioFramesWritten) {
long estimatedAudioLatency = AUDIO_LATENCY_NOT_ESTIMATED;
// First method. SDK >= 19.
if (Build.VERSION.SDK_INT >= 19 && track != null) {
AudioTimestamp audioTimestamp = new AudioTimestamp();
if (track.getTimestamp(audioTimestamp)) {
// Calculate the number of frames between our known frame and the write index
long frameIndexDelta = audioFramesWritten - audioTimestamp.framePosition;
// Calculate the time which the next frame will be presented
long frameTimeDelta = _framesToNanoSeconds(frameIndexDelta);
long nextFramePresentationTime = audioTimestamp.nanoTime + frameTimeDelta;
// Assume that the next frame will be written at the current time
long nextFrameWriteTime = System.nanoTime();
// Calculate the latency
estimatedAudioLatency = nextFramePresentationTime - nextFrameWriteTime;
}
}
// Second method. SDK >= 18.
if (estimatedAudioLatency == AUDIO_LATENCY_NOT_ESTIMATED && Build.VERSION.SDK_INT >= 18) {
Method getLatencyMethod;
try {
getLatencyMethod = AudioTrack.class.getMethod("getLatency", (Class<?>[]) null);
estimatedAudioLatency = (Integer) getLatencyMethod.invoke(track, (Object[]) null) * 1000000L;
} catch (Exception ignored) {}
}
// If no method has successfully gave us a value, let's try a third method
if (estimatedAudioLatency == AUDIO_LATENCY_NOT_ESTIMATED) {
AudioManager audioManager = (AudioManager) CRT.getInstance().getSystemService(Context.AUDIO_SERVICE);
try {
Method getOutputLatencyMethod = audioManager.getClass().getMethod("getOutputLatency", int.class);
estimatedAudioLatency = (Integer) getOutputLatencyMethod.invoke(audioManager, AudioManager.STREAM_MUSIC) * 1000000L;
} catch (Exception ignored) {}
}
// No method gave us a value. Let's use a default value. Better than nothing.
if (estimatedAudioLatency == AUDIO_LATENCY_NOT_ESTIMATED) {
estimatedAudioLatency = DEFAULT_AUDIO_LATENCY;
}
return estimatedAudioLatency
}
private static long _framesToNanoSeconds(long frames) {
return frames * 1000000000L / SAMPLE_RATE;
}
The android MediaPlayer class is notoriously slow to begin audio playback, I experienced an issue in an app I was creating where there was a greater than one second delay to begin playing an audio clip. I resolved it by switching to ExoPlayer which resulted in the playback starting within 100ms. I've also read that ffmpeg has even faster start audio startup time than ExoPlayer but I haven't used it so I can't make any promises.
I'm getting a very peculiar issue with my audio callbacks in my Android app (that's using NDK/OpenSL ES). I'm streaming audio output at 44.1 kHz and 512 frames (which gives me a callback time of 11.6 ms). In the callback, I'm synthesizing a couple of waveforms, filters, etc (like a synthesizer). Due to optimization I never reach over 5 ms of the callback time. However, when I turn on a specific effect (digital delay line), it starts to take a radically longer time in the callback. The digital delay line will jump from 7.5 ms (after all voices/filters have been processed) and jump up to 100 to 350 ms.
This is the most confusing part; after maybe 1 or 2 seconds, the digital delay execution time will jump from the extremely high time to 0.2 ms completion time per callback.
Why would the Android app take a long time to complete my digital delay processing code the first few callbacks and then die down to a very short and audio-happy time? I'm kind of at a loss right now and not sure how to fix this. To confirm, this only happens with the delay processing method. It's just a standard digital delay line (you can find some on github) and I feel like the algorithm isn't the problem here...
Kind of a pseudocode/rough sketch of what my audio callback code looks like:
static bool myAudioCallback(void *userData, short int *audIO, int numSamples, int srate) {
AudioData *data = (AudioData *)userData;
// Resets pointer array values to 0
for (int i = 0; i < numSamples; i++) data->buffer[i] = 0;
// Voice Generation Block
for (int voice = 0; voice < data->numVoices; voice++) {
// Reset voice buffers:
for (int i = 0; i < numSamples; i++) data->voiceBuffer[i] = 0;
// Generate Voice
data->voiceManager[voice]->generateVoiceBlock(data->voiceBuffer, numSamples);
// Sum voices
for (int i = 0; i < numSamples; i++) data->buffer[i] += data->voiceBuffer[i]];
}
// When app first starts, delayEnabled = false so user must click on a
// button on the UI to enable it.
// Trouble is that when we enable processDelay(double *buffer, in frames) the
// first time, we get a long execution time.
if (data->delayEnabled) {
data->delay->processDelay(data->buffer, numSamples);
}
// Conversion loop
for (int i = 0; i < numSamples; i++) {
double sample = clipOutput(data->buffer[i]);
audIO[2*i] = audIO[(2*i)+1] = CONV_FLT_TO_16BIT(sample * data->volume);
}
}
Thanks!
Not a great answer to the solution but this is what I did:
Before the user is able to do anything on the app, I turned on the delay and let it run its course for like 2 seconds before switching it off. This allows the callback to do its weird long 300 ms execution time while not destroying the audio.
Obviously this is not a great answer and if anyone can figure out a more logical explanation I would be more than happy to mark that as the answer.
I am transcoding videos based on the example given by Google (https://android.googlesource.com/platform/cts/+/master/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java)
Basically, transocding of MP4 files works, but on some phones I get some weird results. If for example I transcode a video with audio on an HTC One, the code won't give any errors but the file cannot play afterward on the phone. If I have a 10 seconds video it jumps to almost the last second and you only here some crackling noise. If you play the video with VLC the audio track is completely muted.
I did not alter the code in terms of encoding/decoding and the same code gives correct results on a Nexus 5 or MotoX for example.
Anybody having an idea why it might fail on that specific device?
Best regard and thank you,
Florian
I made it work in Android 4.4.2 devices by following changes:
Set AAC profile to AACObjectLC instead of AACObjectHE
private static final int OUTPUT_AUDIO_AAC_PROFILE = MediaCodecInfo.CodecProfileLevel.AACObjectLC;
During creation of output audio format, use sample rate and channel count of input format instead of fixed values
MediaFormat outputAudioFormat = MediaFormat.createAudioFormat(OUTPUT_AUDIO_MIME_TYPE,
inputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE),
inputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
Put a check just before audio muxing audio track to control presentation timestamps. (To avoid timestampUs X < lastTimestampUs X for Audio track error)
if (audioPresentationTimeUsLast == 0) { // Defined in the begining of method
audioPresentationTimeUsLast = audioEncoderOutputBufferInfo.presentationTimeUs;
} else {
if (audioPresentationTimeUsLast > audioEncoderOutputBufferInfo.presentationTimeUs) {
audioEncoderOutputBufferInfo.presentationTimeUs = audioPresentationTimeUsLast + 1;
}
audioPresentationTimeUsLast = audioEncoderOutputBufferInfo.presentationTimeUs;
}
// Write data
if (audioEncoderOutputBufferInfo.size != 0) {
muxer.writeSampleData(outputAudioTrack, encoderOutputBuffer, audioEncoderOutputBufferInfo);
}
Hope this helps...
If original CTS tests fail you need to go to device vendors and ask for fixes
Using the Cricket Audio Sound Engine ( ios & android) how would I set up a machine gun type sound effect. I need to be able to play many instances of a sound per second. The sound effects need to layer on top of each other.
My solution is to create a new CkSound instance and forget about it. I don't see a easy to destroy the sound, with out a complex sound tracking method. Will this cause memory problems as I am creating thousands of CkSounds over the course of a play session? I really don't want to have to keep track of individual sounds for garbage collection.
// Example sound effect call
void SoundManager::playEffect(const char* name){
// I make a sound , play it , and forget about it
sound = CkSound::newBankSound(g_bank, name);
sound->play();
}
I don’t recommend you create instances and don’t destroy them, as this is a memory leak, so your app will use more and more memory as time goes on.
You could try something like this…
to initialize:
const int k_maxSounds = 5; // maximum number of sound instances to be playing at once
CkSound* g_sounds[k_maxSounds];
for (int i = 0; i < k_maxSounds; ++i)
{
g_sounds[i] = CkSound::newBankSound(g_bank, name);
}
to play another sound instance, find the first available instance and play it:
for (int i = 0; i < k_maxSounds; ++i)
{
if (!g_sounds[i]->isPlaying())
{
g_sounds[i]->play();
break;
}
}
-steve -Cricket Audio Creator answered via email
I'm developing a game in Android and I came across a very annoying, hard-to-find bug. The issue is that when you are using SoundPool to play your sounds, you can actually loop whatever sound you are playing. In this case, the issue is the "running steps" sound; this sound gets executed quite fast and continually (around every 400ms) when the main character is running.
Now when playing the sound on a regular (not so powerful) device e.g. Samsung SII, the sound is played every 500ms - however, if I run the very same code on another device (let's say, Samsung SIV, Samsung SIII), the sound plays twice or even three times faster.
It seems like the more powerful the device hardware specs are, the faster it plays. On some devices, it plays so fast that you almost hear one solid continuous sound. I've been looking for techniques to set a specific ratio on the time period between sound plays, but it doesn't work properly and the issue remains. Does anyone know how to fix it, either using SoundPool, MediaPlayer, or any other sound-controlling API on Android?
You could use an AudioTrack to play a continuous stream of PCM data, since you would pass a stream you could be sure about the interval between sounds. the downside could be a little delay when first starting the sound but it depends on the minimum buffer size, and it depends, I think, on android version and device. On my galaxy s2 android 4.1 it was about 20ms.if you think this could be an option I can post some code
The problem with just looping or using a regular interval for something like footsteps is that you have a possible decoupling of sound and visuals. If your sound gets delays or sped up, or your visuals get delayed or sped up, you would have to adjust for that delay dynamically and automatically. You already have that issue right here
A better solution would be to place a trigger on the exact event which should trigger the sound (in this case, the foot being placed down), which then plays the sound. This also means that if you have multiple sources of the sound (like multiple footsteps), you don't have to manually start the sound with the right interval.
I can't seem to replicate the issue on Galaxy Nexus and Nexus S, does that mean I fixed it? Or maybe you could show what you're doing differently from this:
SoundPool soundPool = new SoundPool(4, AudioManager.STREAM_MUSIC, 100);
Integer sound1 = soundPool.load(this, R.raw.file1, 1);
Integer sound2 = soundPool.load(this, R.raw.file2, 1);
playSound(sound1);
public void playSound(int sound) {
AudioManager mgr = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
float volume = mgr.getStreamVolume(AudioManager.STREAM_MUSIC)
/ mgr.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
soundPool.play(sound, volume, volume, 1, -1, 1.0f);
}
If the problem is that you want to control the interval between the discrete sounds, The easiest way to do this is with a handler.
Basically you start a sound playing which is an asynchronous process. Then you use a handler to schedule a message to play the next sound sometime in the future. It will take some trial and error to get it right, but you will be guaranteed that the sound will start at the same interval after the previous sound on every device.
Here is some code to illustrate what I am talking about.
Here is a handler implementation you could use:
handler = new Handler() {
/* (non-Javadoc)
* #see android.os.Handler#handleMessage(android.os.Message)
*/
#Override
public void handleMessage(Message msg) {
if (msg.what == NEXT_ITEM_MSG) {
playNextSound();
}
else if (msg.what == SEQUENCE_COMPLETE_MSG) {
// notify a listener
listener.onSoundComplete()
}
}
};
Then you could write playNextSound like this:
private void playNextSound() {
if (mRunning) {
// Get the first item
SoundSequenceItem item = currentSequence.getNextSequenceItem();
if (item == null) {
Message msg = handler.obtainMessage(SEQUENCE_COMPLETE_MSG);
handler.sendMessage(msg);
return;
}
// Play the sound
int iSoundResId = item.getSoundResId();
if (iSoundResId != -1) {
player.playSoundNow(soundResId);
}
// schedule a message to advance to next item after duration
Message msg = handler.obtainMessage(NEXT_ITEM_MSG);
handler.sendMessageDelayed(msg, item.getDuration());
}
}
and your SoundSequenceItem could just be a simple class that has a sound file resource id and a duration. If you want to keep playing the sound while the character is moving you could do something like this:
public void onSoundComplete() {
if (character.isRunning()) {
currentSequence.addSequenceItem(new SoundSequenceItem(R.id.footsteps,500);
playNextSound();
}
}
Or you could modify playNextSound to continually play the same sound. Mine is written this way to be able to play different sounds in sequence.
I have had a lot of problems developing apps which used sounds and stuff like that. I would not suggest you to use SoundPool since it is bug-affected, and also be aware that looping sounds with SoundPool won't work on devices which are 4.3 and higher, see this open issue, at AOSP - Issue tracker.
I think that the solution is to go native and use OpenSL ES o similar libraries.