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.
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'm trying to draw audio amplitudes by time, i'm using to achieve this, the AudioRecord class, which gives me a raw audio array.
new Thread(new Runnable() {
#Override
public void run() {
while (mIsRecording) {
int readSize = mRecorder.read(mBuffer, 0, mBuffer.length);
for (int i = 0; i < readSize; i++) {
long time = mChronometer.getTimeElapsed();
ampArray.add((mBuffer[i]));
timeArray.add(time);
}
}
}
}).start();
}
The parameters i use for AudioRecored are:
public static final int SAMPLE_RATE = 8000;
private void initRecorder() {
int bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT);
mBuffer = new short[bufferSize];
mRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT, bufferSize);
}
The result i get is this one:
The result i get -----
What i'm looking for
Am I missing something here?
Thanks in advance.
EDIT: Drawing method:
When the recording is stopped, i send all the values saved in the amplitude array and the one in the time array to the LineGraphSeries of the GraphView Api
series = new LineGraphSeries<DataPoint>(generateData(ampArray, timeArray));
graph.addSeries(series);
generateData method:
double x = 0; int i = 0; short y = 0;
private DataPoint[] generateData(ArrayList<Short> ampArray, ArrayList<Double> timeArray) {
DataPoint[] values = new DataPoint[ampArray.size()];
for (int i=0; i< ampArray.size(); i++) {
x = timeArray.get(i);
y = ampArray.get(i);
DataPoint v = new DataPoint(x, y);
values[i] = v;
}
return values;
}
I'm going to take an educated guess here and suggest that it has something to do with these two lines:
long time = mChronometer.getTimeElapsed();
timeArray.add(time);
It looks to me like you are trying to plot the samples which have occurred in a different time regime and you are processing in batch against the current CPU clock which would explain your results - for example you might process a big block of samples - which you can do much faster than they occurred in the first place - and they might all get a similar time axis value.
The proper approach is to reconstruct the time axis for the samples themselves. Assume the first sample you process is time 0. If your sample rate is 48000 then each sample is 1/48000 of a second. The approach would be something like this:
int sampleNumber = 0;
while (mIsRecording) {
int readSize = mRecorder.read(mBuffer, 0, mBuffer.length);
for (int i = 0; i < readSize; i++) {
ampArray.add((mBuffer[i]));
double time = sampleNumber / SAMPLE_RATE;
timeArray.add(time);
sampleNumber++;
}
}
Note, I changed timeArray from int to double as it is now in seconds rather than milliseconds. If you prefer milliseconds then multiply time by 1000 and cast to a long.
Also, you don't need to create an array for the time axis as you can determine the time of any sample based upon its absolute index in the ampArray.
I'm trying to generate and play a square wave with AudioTrack(Android). I've read lots of tutorials but still have some confusions.
int sampleRate = 44100;
int channelConfig = AudioFormat.CHANNEL_IN_MONO;
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
AudioTrack audioTrack;
int buffer = AudioTrack.getMinBufferSize(sampleRate, channelConfig,
audioFormat);
audioTrack.write(short[] audioData, int offsetInShorts, int sizeInShorts);
In the codes, what makes me confused is How to write the short array "audioData" ...
Anyone can help me? Thanks in advance !
You should use Pulse-code modulation. The linked article has an example of encoding a sine wave, a square wave is even simpler. Remember that the maximum amplitude is encoded by the maximum value of short (32767) , and that the "effective" frequency depends on your sampling rate.
This method generates Square, Sin and Saw Tooth wave forms
// Process audio
protected void processAudio()
{
short buffer[];
int rate =
AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);
int minSize =
AudioTrack.getMinBufferSize(rate, AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT);
// Find a suitable buffer size
int sizes[] = {1024, 2048, 4096, 8192, 16384, 32768};
int size = 0;
for (int s : sizes)
{
if (s > minSize)
{
size = s;
break;
}
}
final double K = 2.0 * Math.PI / rate;
// Create the audio track
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, rate,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT,
size, AudioTrack.MODE_STREAM);
// Check audiotrack
if (audioTrack == null)
return;
// Check state
int state = audioTrack.getState();
if (state != AudioTrack.STATE_INITIALIZED)
{
audioTrack.release();
return;
}
audioTrack.play();
// Create the buffer
buffer = new short[size];
// Initialise the generator variables
double f = frequency;
double l = 0.0;
double q = 0.0;
while (thread != null)
{
// Fill the current buffer
for (int i = 0; i < buffer.length; i++)
{
f += (frequency - f) / 4096.0;
l += ((mute ? 0.0 : level) * 16384.0 - l) / 4096.0;
q += (q < Math.PI) ? f * K : (f * K) - (2.0 * Math.PI);
switch (waveform)
{
case SINE:
buffer[i] = (short) Math.round(Math.sin(q) * l);
break;
case SQUARE:
buffer[i] = (short) ((q > 0.0) ? l : -l);
break;
case SAWTOOTH:
buffer[i] = (short) Math.round((q / Math.PI) * l);
break;
}
}
audioTrack.write(buffer, 0, buffer.length);
}
audioTrack.stop();
audioTrack.release();
}
}
Credit goes to billthefarmer.
Complete Source code:
https://github.com/billthefarmer/sig-gen
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.
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.