AudioTrack: start called from a thread - android

I using AudioTrack in static mode to reproduce the same signal over and over again.
I have followed the example in here and sometimes it works perfectly, but sometimes it throws this error and it produces no sound:
AudioTrack: start called from a thread
01-23 15:26:16.902: W/libutils.threads(1133): Thread (this=0x3973b8): don't call waitForExit() from this Thread object's thread. It's a guaranteed deadlock!
This is the source code. I'm trying to ensure that I call stop and reload the data for the next "play" execution.
public class SoundPlayer {
// originally from http://marblemice.blogspot.com/2010/04/generate-and-play-tone-in-android.html
private int numSamples;
private double sample[];
private byte generatedSnd[];
private AudioTrack audioTrack;
public SoundPlayer(float duration, int sampleRate, double freqOfTone) {
super();
this.numSamples = (int) (duration * sampleRate);
this.sample = new double[numSamples];
this.generatedSnd = new byte[2 * numSamples];
// fill out the array
for (int i = 0; i < numSamples; ++i) {
sample[i] = Math.sin(2 * Math.PI * i / (sampleRate / freqOfTone));
}
// convert to 16 bit pcm sound array
// assumes the sample buffer is normalised.
int idx = 0;
for (final double dVal : sample) {
// scale to maximum amplitude
final short val = (short) ((dVal * 32767));
// in 16 bit wav PCM, first byte is the low order byte
generatedSnd[idx++] = (byte) (val & 0x00ff);
generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
}
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
sampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT, numSamples,
AudioTrack.MODE_STATIC);
audioTrack.write(generatedSnd, 0, generatedSnd.length);
}
public void playSound() {
if ( audioTrack.getPlayState() == (AudioTrack.PLAYSTATE_PLAYING | AudioTrack.PLAYSTATE_PAUSED )) {
audioTrack.stop();
audioTrack.reloadStaticData();
}
Log.i("Audio", "playState: " + audioTrack.getPlayState());
audioTrack.play();
audioTrack.stop();
audioTrack.reloadStaticData();
}
}
If we open the android source code, it does not explains a lot:
void AudioTrack::start()
{
sp<AudioTrackThread> t = mAudioTrackThread;
LOGV("start");
if (t != 0) {
if (t->exitPending()) {
if (t->requestExitAndWait() == WOULD_BLOCK) {
LOGE("AudioTrack::start called from thread");
return;
}
}
t->mLock.lock();
}
Does anyone know how to handle this?

I had a similar problem once.
Simply put, if you happen to have stuff running in more than one thread you have to make sure that creating the AudioTrack and calling play() and stop() on it must happen in the same thread.
However, it doesn't mean that you have to create the audio samples in that thread, too. In case you use static audio data (AudioTrack.MODE_STATIC) you can preload or generate them elsewhere and then use it even multiple times throughout the lifetime of your app.

Related

Android: confused about how to get amplitude of a frequency generated and played by AudioTrack

In my app I generate list of sounds with different frequencies, for example 1000Hz, 2000Hz, 4000Hz ... for left and right channel:
private AudioTrack generateTone(Ear ear) {
// fill out the array
int numSamples = SAMPLE_RATE * getDurationInSeconds();
double[] sample = new double[numSamples];
byte[] generatedSnd = new byte[2 * numSamples];
for (int i = 0; i < numSamples; ++i) {
sample[i] = Math.sin(2 * Math.PI * i / (SAMPLE_RATE / getLatestFreqInHz()));
}
// convert to 16 bit pcm sound array
// assumes the sample buffer is normalised.
int idx = 0;
for (final double dVal : sample) {
// scale to maximum amplitude
final short val = (short) ((dVal * 32767));
// in 16 bit wav PCM, first byte is the low order byte
generatedSnd[idx++] = (byte) (val & 0x00ff);
generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
}
AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT, numSamples,
AudioTrack.MODE_STATIC);
audioTrack.write(generatedSnd, 0, generatedSnd.length);
int channel;
if (ear == Ear.LEFT) {
audioTrack.setStereoVolume(1.0f, 0.0f);
} else if (ear == Ear.RIGHT) {
audioTrack.setStereoVolume(0.0f, 1.0f);
}
return audioTrack;
}
I use this code to control volume:
audioManager = (AudioManager) context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
MAX_VOLUME = audioManager.getStreamMaxVolume(STREAM_MUSIC);
MIN_VOLUME = audioManager.getStreamMinVolume(STREAM_MUSIC);
APP_DEFAULT_VOLUME = (MAX_VOLUME + MIN_VOLUME) /2 ;
#Override
public void setToAppDefaultVolume() {
Timber.tag(TAG).d("Resetting to app default sound volume.");
audioManager.setStreamVolume(STREAM_MUSIC, APP_DEFAULT_VOLUME, 0);
}
#Override
public void increaseVolume() {
if (!isReachedMaxVolume()) {
Timber.tag(TAG).d("Increasing device sound volume from %d to %d", currentVolumeLevel(), currentVolumeLevel() + STEP);
audioManager.setStreamVolume(STREAM_MUSIC, currentVolumeLevel() + STEP, 0);
} else {
Timber.tag(TAG).d("Reached the maximum device volume.");
}
}
#Override
public void decreaseVolume() {
if (!isReachedMinVolume()) {
Timber.tag(TAG).d("Decreasing device sound volume from %d to %d", currentVolumeLevel(), currentVolumeLevel() - STEP);
audioManager.setStreamVolume(STREAM_MUSIC, currentVolumeLevel() - STEP, 0);
} else {
Timber.tag(TAG).d("Reached the preferred minimum volume");
}
}
I start playing each tone (frequency) with APP_DEFAULT_VOLUME and gradually increase tone. When user confirm he/she heard specific tone with specific volume, I want to calculate it's amplitude and log it for latter so I can review user hearing ...
But i have no clue how to do so!
All solutions I found was about reading data from microphone and calculate the amplitude to visualize it on screen ...
My scenario is much simpler. I have device volume recorded, frequency is fixed and recorded, and Audio is generated dynamically and is not a file.
Will anyone help me with this scenario ?
Thanks in advance.

Play short generated sound

I want to play a generated sound that is shorter than 1 second. However, the minBufferSize of the AudioTrack always seems to be 1 second or longer. On some devices I can set the bufferSize smaller than the value evaluated with AudioTrack.getMinBufferSize, however this is not possible on all devices. I'd like to know wether it's possible to generate a shorter sound for the AudioTrack. I'm currently using this code (it contains some smoothing, because I'm getting constantly new frequences):
int buffSize = AudioTrack.getMinBufferSize(sampleRate,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT);
AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT, buffSize,
AudioTrack.MODE_STREAM);
short samples[] = new short[buffSize];
int amp = 10000;
double twopi = 8. * Math.atan(1.);
double phase = 0.0;
audioTrack.play();
double currentFrequency = getFrequency();
double smoothing = 300;
double deltaTime = buffSize / 500;
while (playing && PreferenceManager.getDefaultSharedPreferences(
MainActivity.this).getBoolean("effect_switch", true))
{
double newFrequency = getFrequency();
for (int i = 0; i < buffSize; i++)
{
currentFrequency += deltaTime * (newFrequency - currentFrequency) / smoothing;
samples[i] = (short) (amp * Math.sin(phase));
phase += twopi * currentFrequency / sampleRate;
}
audioTrack.write(samples, 0, buffSize);
}
audioTrack.stop();
audioTrack.release();
In fact, I want the sounds to be updated more frequently, which is the reason for me needing shorter samples.
I think I have a solution for you. Since my min buffer seems to be much smaller than 1 sec, I simulated your problem by loading a buffer with 5 sec of data but only play 0.5 sec of it immediately followed by another frequency. This tone I also created 5 sec of data but only played 0.5 sec & repeated this for several tones. It all works for me.
Also, since I jammed this into a current project I'm working on, it's difficult for me to just cut and paste my code. While I've tested my solution, what I've posted here is not tested exactly as written. Some of it is cut & paste, some pseudocode.
The key feature is using the OnPlaybackPositionUpdateListener.
private AudioTrack.OnPlaybackPositionUpdateListener audioTrackListener = new AudioTrack.OnPlaybackPositionUpdateListener() {
#Override
public void onMarkerReached(AudioTrack audioTrack) {
int marker = audioTrack.getNotificationMarkerPosition();
// I just used 8 tones of 0.5 sec each to determine when to stop but you could make
// the condition based on a button click or whatever is best for you
if(marker < MAX_FRAME_POSITION) {
audioTrack.pause();
newSamples();
audioTrack.play();
} else {
audioTrack.stop();
}
audioTrack.setNotificationMarkerPosition(marker + FRAME_MARKER);
Log.d(TAG, "MarkerReached");
}
#Override
public void onPeriodicNotification(AudioTrack audioTrack) {
int position = audioTrack.getPlaybackHeadPosition();
if(position < MAX_FRAME_POSITION) {
audioTrack.pause();
newSamples();
audioTrack.play();
} else {
audioTrack.stop();
}
Log.d(TAG, "PeriodNotification");
}
};
Then
audioTrack.setPlaybackPositionUpdateListener(AudioTrackListener);
I used the marker (which has to be re-initialized repeatedly) for my tests...
audioTrack.setNotificationMarkerPosition(MARKER_FRAMES);
but you should be able to use the periodic notification too.
audioTrack.setPositionNotificationPeriod(PERIODIC_FRAMES);
And the newSamples() method called from the listener
public void newSamples() {
/*
* generate buffer, I'm doing similar to you, without the smoothing
*/
// AudioTrack write is a blocking operation so I've moved it off to it's own Thread.
// Could also be done with an AsyncTask.
Thread thread = new Thread(writeSamples);
thread.start();
}
private Runnable writeSamples = new Runnable() {
#Override
public void run() {
audioTrack.write(samples, 0, buffSize);
}
};

AudioTrack how to play more than 32 sounds for guitar app

I am a high school student currently working on an arduidroid project using potentiometers, bluetooth, and a guitar app. My issue is that when I play my guitar app that I get an error that won't crashes.
AudioFlinger could not create track, status: -12
Error -12 initializing AudioTrack
Error code -20 when initializing AudioTrack.
How do I make it so that I can play as many sounds as I want with 3 different buttons using a tone generator from this question.
void genTone(double freq){
// fill out the array
for (int i = 0; i < numSamples; ++i) {
sample[i]= 2*(i%(sampleRate/freq))/(sampleRate/freq)-1;
}
// convert to 16 bit pcm sound array
// assumes the sample buffer is normalised.
int idx = 0;
for (final double dVal : sample) {
// scale to maximum amplitude
final short val = (short) ((dVal * 32767));
// in 16 bit wav PCM, first byte is the low order byte
generatedSnd[idx++] = (byte) (val & 0x00ff);
generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
}
}
void playSound(){
final AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
sampleRate, AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT, generatedSnd.length,
AudioTrack.MODE_STATIC);
audioTrack.write(generatedSnd, 0, generatedSnd.length);
audioTrack.play();
}
if(BString.isPressed()){
/*genTone(BStringFreq);
playSound();*/
final Thread thread = new Thread(new Runnable() {
public void run() {
genTone(BStringFreq);
handler.post(new Runnable() {
public void run() {
playSound();
}
});
}
});
thread.start();
}
}
Here are some snippets of my code. Please help me out here as I am lost on this subject.

measure running time of a method in Android

i have a method that generates an audio signal from a smartphone's speaker, i set the duration time for 1 second. i want to know if the played sound is really played for one second or not.
for that i created a playSound() method and used getTimeInMillis() method to measure the time.
the result was varying from 11 ms to 679 ms. it seemed to me that it just measures the excution time of the method not the whole running time (the time till the playSound() stops).
is there a way to measure that time in java?
void genTone() throws IOException{
double instfreq=0, numerator;
for (int i=0;i<numSample; i++ )
{
numerator=(double)(i)/(double)numSample;
instfreq =freq1+(numerator*(freq2-freq1));
if ((i % 1000) == 0) {
Log.e("Current Freq:", String.format("Freq is: %f at loop %d of %d", instfreq, i, numSample));
}
sample[i]=Math.sin(2*Math.PI*i/(sampleRate/instfreq));
}
int idx = 0;
for (final double dVal : sample) {
// scale to maximum amplitude
final short val = (short) ((dVal * 32767)); // max positive sample for signed 16 bit integers is 32767
// in 16 bit wave PCM, first byte is the low order byte (pcm: pulse control modulation)
generatedSnd[idx++] = (byte) (val & 0x00ff);
generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
}
DataOutputStream dd=new DataOutputStream( new FileOutputStream(Environment.getExternalStorageDirectory().getAbsolutePath()+"/tessst.wav" ));
dd.writeBytes("RIFF");
dd.writeInt(48000); // Final file size not known yet, write 0
dd.writeBytes("WAVE");
dd.writeBytes("fmt ");
dd.writeInt(Integer.reverseBytes(16)); // Sub-chunk size, 16 for PCM
dd.writeShort(Short.reverseBytes((short) 1)); // AudioFormat, 1 for PCM
dd.writeShort(Short.reverseBytes( (short) myChannels));// Number of channels, 1 for mono, 2 for stereo
dd.writeInt(Integer.reverseBytes(sampleRate)); // Sample rate
dd.writeInt(Integer.reverseBytes( (sampleRate*myBitsPerSample*myChannels/8))); // Byte rate, SampleRate*NumberOfChannels*BitsPerSample/8
dd.writeShort(Short.reverseBytes((short) (myChannels*myBitsPerSample/8))); // Block align, NumberOfChannels*BitsPerSample/8
dd.writeShort(Short.reverseBytes( myBitsPerSample)); // Bits per sample
dd.writeBytes("data");
dd.write(generatedSnd,0,numSample);
dd.close();
void playSound(){
AudioTrack audioTrack= null;
try{
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,sampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, generatedSnd.length, AudioTrack.MODE_STATIC);
audioTrack.write(generatedSnd, 0, generatedSnd.length);
audioTrack.play();
}
catch(Exception e)
{
System.out.print(e);
}
}
void excute() throws IOException
{
long time=Calendar.getInstance().getTimeInMillis();
playSound();
time=Calendar.getInstance().getTimeInMillis()-time;
Log.e("time", String.format(" "+time+" ms"));
}

about "Playing an arbitrary tone with Android"

Could not find how to ask a question about "Playing an arbitrary tone with Android" post by Steve Pomeroy, so started one here.
Is there any code that needs to be added to an xml file?
Could not get the sim to make sound.
public class PlaySound extends Activity {
// originally from http://marblemice.blogspot.com/2010/04/generate-and-play-tone-in-android.html
// and modified by Steve Pomeroy <steve#staticfree.info>
private final int duration = 3; // seconds
private final int sampleRate = 8000;
private final int numSamples = duration * sampleRate;
private final double sample[] = new double[numSamples];
private final double freqOfTone = 440; // hz
private final byte generatedSnd[] = new byte[2 * numSamples];
Handler handler = new Handler();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
#Override
protected void onResume() {
super.onResume();
// Use a new tread as this can take a while
final Thread thread = new Thread(new Runnable() {
public void run() {
genTone();
handler.post(new Runnable() {
public void run() {
playSound();
}
});
}
});
thread.start();
}
void genTone(){
// fill out the array
for (int i = 0; i < numSamples; ++i) {
sample[i] = Math.sin(2 * Math.PI * i / (sampleRate/freqOfTone));
}
// convert to 16 bit pcm sound array
// assumes the sample buffer is normalised.
int idx = 0;
for (final double dVal : sample) {
// scale to maximum amplitude
final short val = (short) ((dVal * 32767));
// in 16 bit wav PCM, first byte is the low order byte
generatedSnd[idx++] = (byte) (val & 0x00ff);
generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
}
}
void playSound(){
final AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
sampleRate, AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT, generatedSnd.length,
AudioTrack.MODE_STATIC);
audioTrack.write(generatedSnd, 0, generatedSnd.length);
audioTrack.play();
}
}
is there something like:
activity android:soundEffectsEnabled="true"
or
uses-permission android:name="android.permission.WRITE_SETTINGS"
that needs to be added so the above code will make sound in a simulator such as eclipse?
i have added activity android:soundEffectsEnabled="true" and uses-permission android:name="android.permission.WRITE_SETTINGS"/, but still will not make sound.
thought it was duration of sound, because when duration was set to 10 instead of 1, it made a beep, but was very short. however, after the third time of running it, an inflateException is thrown.
duration over 500 causes an out of memory error, which is what through the exception. however, duration of 100 still only makes a very short beep, can barley hear it, the mouse click is louder.
duration of over 250 is a memory hug.
duration of 10 makes as long of a click as duration of 250.
generatedSnd.length of 10 makes as long of a click as generatedSnd.length of 15k
have changed the freqOfTone from 100 up to 55000.
still can not figure out how to make sound longer.
adding
int x = 0;
// Montior playback to find when done
do
{
if (audioTrack != null)
x = audioTrack.getPlaybackHeadPosition();
else
x = numSamples;
}
while (x<numSamples);
// Track play done. Release track.
if (audioTrack != null) audioTrack.release();
after
audioTrack.play();
stops the short clicking after the first time it is run.
now i have to find out why it is not working when i change the freqOfTone.

Categories

Resources