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.
Related
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.
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);
}
};
I'm building an android app that pulses an icon - simple pulse, 2x size at loudest volume and 1x at no volume - based on audio. Worth noting my min api is 15.
The user selects the mode (file)to play and I use AudioTrack to play it back on an infinite loop. Each wav sample ranges from < second to 2 or 3 seconds. Audiotrack lets me set the volume and pitch in real-time based on user input (SoundPool wasn't correctly changing pitch in Kitkat).
As the volume changes within each audiotrack, I'm trying to shrink and grow the icon. So far I've tried visualizer to get the waveform and fft data as the track is playing, but I'm not sure that's correct.
Is there a way to get the (nearest possible) real-time db changes from an audiotrack? The wave form function seems to always be between 108 and 112, so I don't think I'm using it correctly. The easiest pulse.wav example is here
My audiotrack init using a byte[] from pcm data
AudioTrack mAudioTrack = new AudioTrack(AudioAudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, getMinBuffer(sound), AudioTrack.MODE_STATIC);
mAudioTrack.write(mSound, 0, mSound.length);
mAudioTrack.setLoopPoints(0, (int)(mSound.length / 4), -1);
My Visualizer
Visualizer mVisualizer = new Visualizer(mAudioTrack.getAudioSessionId());
mVisualizer.setEnabled(false);
mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
mVisualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener {
#Override
public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes, int samplingRate) {
double sum = 0;
for (int i = 0; i < bytes.length; i++) {
sum += Math.abs(bytes[i]) * Math.abs(bytes[i]);
}
double volume = (double) Math.sqrt(1.0d * sum / bytes.length);
//THIS IS THE RESIZE FUNCTION//
//resizeHeart((double) volume);
System.out.println("Volume: " + volume); //always prints out between 108 and 112.
}
#Override
public void onFftDataCapture(Visualizer visualizer, byte[] bytes, int samplingRate) {
//not sure what to do here.
}
}, Visualizer.getMaxCaptureRate() / 2, true, true);
mVisualizer.setEnabled(true);
The problem is that you're treating the bytes as samples even though you've specified a 16-bit sample size. Try something like this (note the abs is unnecessary since you're squaring anyway):
for (int i = 0; i < bytes.length/2; i+=2) {
int sample = bytes[i] << 8 || bytes[i+1];
sum += sample * sample;
}
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.
I'm building an application that is always recording audio from the microphone, whenever the audio reaches a certain threshold I perform a certain action.
However how should I calc the appropriate volume for the threshold ? I've a static volume coded which works well across some devices but not all devices (in somes cases it is too sensitive or vice versa).
I'm using AudioRecord, here's part of the code:
int bufferSize = AudioRecord.getMinBufferSize(Constants.RECORDING_FREQUENCY,Constants.channelConfiguration,Constants.audioEncoding);
AudioRecord audioRecord = new AudioRecord( MediaRecorder.AudioSource.MIC,Constants.RECORDING_FREQUENCY,Constants.channelConfiguration,Constants.audioEncoding, bufferSize);
short[] buffer = new short[bufferSize];
while(true) {
int bufferReadResult = audioRecord.read(buffer, 0, bufferSize);
for (int i = 0; i < bufferReadResult; i++) {
currentVolume = java.lang.Math.abs(buffer[i]);
if (currentVolume > Constants.NO_VOLUME_AMPLITUDE)
// ALRIGHT ! This is what I'm looking for :D
}
}
So, my question is: how do I calculate Constants.NO_VOLUME_AMPLITUDE instead of having it hard coded ?
Thanks so much in advance,
Ze