about "Playing an arbitrary tone with Android" - 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.

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

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.

generate chirp signals in android

i have been trying to generate a linear Chirp signal using smartphone speakers. i wrote a code following this equation that i found here.
the equation looks logical to me but when i tried to test on high frequencies like 24KHz or 26KHz the sound is still audible and gets out of the speakers though my smartphone doesnt support frequencies over 22KHz.
Please if you notice anything wrong with my code, you will be giving me a great help.
public class MainActivity extends Activity {
int duration=1;
int sampleRate=44100;
int numSample=duration*sampleRate;
double sample[]=new double[numSample];
double freq1=23000;
double freq2=24000;
byte[] generatedSnd= new byte[2*numSample];
Handler handler=new Handler();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Thread thread=new Thread(new Runnable(){
public void run(){
try {
genTone();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
handler.post(new Runnable(){
public void run(){
playSound();
}
});
}
});
thread.start();
}
protected void onResume()
{
super.onResume();
}
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);
}
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();
What you are experiencing is a phenomenon known as aliasing. For more information, read about the Nyquist Theorem. Essentially what this means is that you can reproduce any frequency up to 1/2 of the sample rate. But once you cross that threshold, the frequency begins folding back such that a sine at sample rate plus 1kHz is indistinguishable from a sine at sample rate minus 1kHz, and so on. It's very similar to something you might have seen on film where a car wheel appears to stop or even move backward (the wagon wheel effect). The best solution to your problem is to prevent the user from entering a frequency greater than half the sample rate.

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