Play short generated sound - android

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

Related

How to generate a tone of specified frequncy for a fixed duration

I am new to android.
Can someone help me with a non-deprecated code to generate a tone of F frequency and S second duration
I found this but it uses deprecated methods.
Here is a rendition of the linked code, adapted for API level 23+, with a parameter added for tone duration:
short generatedSnd[];
int sampleRate = 44100;
float freqOfTone = 1000; //Hz
float amplitude = 10000; //0..32767 (0x7FFF)
float lengthSeconds = 1.0f;
private void genTone(){
int numSamples = (int) (sampleRate * lengthSeconds);
generatedSnd = new short[numSamples];
// fill out the array
for (int i = 0; i < numSamples; ++i) {
generatedSnd[i] = (short) (Math.sin(2 * Math.PI * i / (sampleRate/freqOfTone)) * amplitude);
}
}
private void playSound(){
AudioTrack audioTrack = new AudioTrack.Builder()
.setAudioFormat(
new AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(sampleRate)
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.build()
)
.setTransferMode(AudioTrack.MODE_STATIC)
.setBufferSizeInBytes(generatedSnd.length * 2) //2 bytes-per-sample
.build();
audioTrack.write(generatedSnd, 0, generatedSnd.length);
audioTrack.play();
}
This code has been tested up to 50s in the emulator, but it may crash at higher play times, due to the size being passed to setBufferSize(). Note it does not include other advanced features to help you handle arbitrarily long play times, such as 1.) streaming playback, or 2.) phase correction due to float quantization errors at values far from 0.
It should work fine for durations less than about 10 seconds, however.
Also omitted is the AudioTrack cleanup code (stop()/release()).

Android sound wave - not smooth and duration issue

I have a problem to generate smooth sinus wave.
I've done it few years ago on C++ and everything worked perfect. Now I am trying to do this using AudioTrack and I do not know what is wrong.
This is my test case:
I want to produce for five second a sinus wave which is smooth (no crack etc.). For one second I generate 44100 samples and divided it on couple of buffer with size 8192 (probably this is the reason of cracks, but how can I fix it, without giving bigger size of buffer).
Unfortunatelly using my code the sound is not smooth and instead of 5 second it takes about 1 second. I would be gratefull for any help.
Please let me now if this piece of code is not enough.
class Constants:
//<---
public final static int SAMPLING = 44100;
public final static int DEFAULT_GEN_DURATION = 1000;
public final static int DEFAULT_NUM_SAMPLES = DEFAULT_GEN_DURATION * SAMPLING / 1000; //44100 per second
public final static int DEFAULT_BUFFER_SIZE = 8192;
//--->
//preparing buffers to play;
Buffer buffer = new Buffer();
short[] buffer_values = new short[Constants.DEFAULT_BUFFER_SIZE];
float[] samples = new float[Constants.DEFAULT_BUFFER_SIZE];
float d = (float) (( Constants.FREQUENCIES[index] * 2 * Math.PI ) / Constants.SAMPLING);
int numSamples = Constants.DEFAULT_NUM_SAMPLES; //44100 per second - for test
float x = 0;
int index_in_buffer = 0;
for(int i = 0; i < numSamples; i++){
if(index_in_buffer >= Constants.DEFAULT_BUFFER_SIZE - 1){
buffer.setBufferShort(buffer_values);
buffer.setBufferSizeShort(index_in_buffer);
queue_with_data_AL.add(buffer); //add buffer to queue
buffer_values = new short[Constants.DEFAULT_BUFFER_SIZE];
samples = new float[Constants.DEFAULT_BUFFER_SIZE];
index_in_buffer = 0;
}
samples[index_in_buffer] = (float) Math.sin(x);
buffer_values[index_in_buffer] = (short) (samples[index_in_buffer] * Short.MAX_VALUE);
x += d;
index_in_buffer++;
}
buffer.setBufferShort(buffer_values);
buffer.setBufferSizeShort(index_in_buffer+1);
queue_with_data_AL.add(buffer);
index_in_buffer = 0;
}
//class AudioPlayer
public AudioPlayer(int sampleRate) { //44100
int minSize = AudioTrack.getMinBufferSize(sampleRate,
AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
audiotrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT,
minSize, AudioTrack.MODE_STREAM);
}
public void play(byte[] audioData, int sizeOfBuffer) {
audiotrack.write(audioData, 0, sizeOfBuffer);
}
public void start() {
if (state == Constants.STOP_STATE) {
state = Constants.START_STATE;
int startLength = 0;
while (state == Constants.START_STATE) {
Buffer buffer = getBufferFromQueueAL(); //getting buffer from prepared list
if (buffer != null) {
short[] data = buffer.getBufferShort();
int size_of_data = buffer.getBufferSizeShort();
if (data != null) {
int len = audiotrack.write(data, 0, size_of_data);
if (startLength == 0) {
audiotrack.play();
}
startLength += len;
} else {
break;
}
} else {
MessagesLog.e(TAG, "get null data");
break;
}
}
if (audiotrack != null) {
audiotrack.pause();
audiotrack.flush();
audiotrack.stop();
}
}
}
You are playing only 1 second because 44100 samples at a samplerate of 44100Hz result in exactly 1 second of sound.
You have to generate 5 times more samples if you want to play 5 seconds of sound (e.g. multiply DEFAULT_NUM_SAMPLES by 5) in your code.
I've found the solution by myself. After adding Buffer to queue_with_data_AL I've forgotten create new instance of Buffer object. So in queue was couple of buffer with the same instance, hence sinus wave were not continuous.
Thanks if someone was trying to solve my problem. Unfortunatelly it was my programming mistake.
Best regards.

Lag in AudioTrack.write function android

Scenario:
There is a imageview which implements ontouchlistener. When the user touches on the screen i am starting a separate thread which will be generating sin wave and i am writing it to the audiotrack here is the code of generation of sin wave.
t = new Thread() {
public void run() {
setPriority(Thread.MAX_PRIORITY);
// set the buffer size
int buffsize = AudioTrack.getMinBufferSize(sampleRate,
AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
// create an audiotrack object
AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
samplerate, AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT, buffsize,
AudioTrack.MODE_STREAM);
float amp = (float)10000;
double twopi = 2*Math.PI;
// start audio
audioTrack.play();
short samples[] = new short[buffsize];
// synthesis loop
while(isRunning){
for(int i=0; i < buffsize; i++){
samples[i] = (short) (amp*Math.sin(ph));
ph += (twopi*fr)/sr;
if (ph > twopi)
{
ph -= twopi;
}
if(isRunning==false)
break;
}
if(isRunning==false)
break;
audioTrack.write(samples, 0, buffsize);
}
audioTrack.stop();
audioTrack.release();
}
};
As the user moves his hand on ACTION_MOVE call back i am changing the frequency . Frequency mainly varies on the distance moved by the user from the initial point.
isRunnig will become true on ACTION_UP callback.
Problem:
I am missing the some of the frequencies. I mean if the user moves his hand very fastly some of the frequency values are getting missed.
This is mainly because of audioTrack.write(samples, 0, buffsize) This function takes some time..
i.e if i get 4 time ACTION_MOVE callback, 4 points i will get, 4 time the frequency gets varied. But audio track gets updated 2 times. What i have to do to remove this lag. Is there any better approach to do this.

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.

AudioTrack: start called from a thread

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.

Categories

Resources