I am trying to build an android app which synthesizes and plays musical notes. I am using AudioTrack. It seems that my program is unable to fill the track buffer fast enough due to the latency in generating the audio information. I am generating the audio info from the fourier coefficients of the note. Hence before each write, the program has to create and sample 2048 sine waves each with a sample size of 2400 and sample rate of 44100!
So instead of a continuous sound I hear intermittent beeps. The Logcat gives the following warning in between beeps:
W/AudioTrack﹕ obtainBuffer() track 0x177ff80 disabled, restarting
My code is given below. Can anyone identify ways in which I could optimize the code?
package com.alantaar.alantaar;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import java.util.Arrays;
public class AudioGenerator {
private AudioTrack audio;
private double[] coefficients;
private int bufferSize;
private int sampleRate;
private double[] phases;
private double frequency;
public AudioGenerator(double[] coefficients, double frequency, int sampleRate, int bufferSize){
int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT);
this.bufferSize = bufferSize > minBufferSize ? bufferSize : minBufferSize;
audio = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT, this.bufferSize, AudioTrack.MODE_STREAM);
this.coefficients = coefficients;
this.sampleRate = sampleRate;
this.frequency = frequency;
this.phases = new double[coefficients.length];
Arrays.fill(this.phases, 0.0);
Log.i("length", Integer.toString(coefficients.length));
audio.play();
}
public void playNote(){
while(true){
short[] waveSample = sampleWave();
audio.write(waveSample, 0, waveSample.length);
}
}
private short[] sampleWave(){
short [] waveSample = new short[bufferSize];
Arrays.fill(waveSample, (short)0);
for(int i = 0; i < coefficients.length; i++){
double coefficient = coefficients[i];
short[] sineSamples = sampleSineWave(coefficient, i);
for(int j = 0; j < waveSample.length; j++){
waveSample[j] += sineSamples[j];
}
}
return waveSample;
}
private short [] sampleSineWave(double coefficient, int index){
double freq = frequency * index;
short [] samples = new short[bufferSize];
for(int i = 0; i < bufferSize; i++){
samples[i] = (short) (coefficient * Short.MAX_VALUE * Math.cos(phases[index]));
phases[index] += 2 * Math.PI * freq/sampleRate;
}
return samples;
}
public void pauseNote(){
audio.stop();
}
public void stopNote(){
audio.release();
}
}
Here are a few things that pop out at me:
Move computations out of the inner loop. For example, the 2*pi*freq/sampleRate could be done right before the for. Same with coefficient * Short.MAX_VALUE.
I'd bet that your note doesn't actually contain 2048 different meaningful sine components.One option would be to NOT generate tones when the coefficient falls below a certain threshold.
Use FFT! You are implementing a DFT which is O(n^2). FFT runs in O(n log n).
Use the time while audio.write is blocking to generate more samples. I don't know the behavior of AudioTrack's streaming mode but there is a good chance that your program is spending most of its time in a blocking call.
Related
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 have a sample code that fixed sampling rate, fft point in audio recording. This code is
private static final String FILE_NAME = "audiorecordtest.raw";
private static final int SAMPLING_RATE = 44100;
private static final int FFT_POINTS = 1024;
private static final int MAGIC_SCALE = 10;
private void proceed() {
double temp;
Complex[] y;
Complex[] complexSignal = new Complex[FFT_POINTS];
for (int i=0; i<FFT_POINTS; i++) {
temp = (double)((audioBuffer[2*i] & 0xFF) | (audioBuffer[2*i+1] << 8)) / 32768.0F;
complexSignal[i] = new Complex(temp * MAGIC_SCALE, 0d);
}
y = FFT.fft(complexSignal);
/*
* See http://developer.android.com/reference/android/media/audiofx/Visualizer.html#getFft(byte[]) for format explanation
*/
final byte[] y_byte = new byte[y.length*2];
y_byte[0] = (byte) y[0].re();
y_byte[1] = (byte) y[y.length - 1].re();
for (int i = 1; i < y.length - 1; i++) {
y_byte[i*2] = (byte) y[i].re();
y_byte[i*2+1] = (byte) y[i].im();
}
if (handler != null) {
handler.onFftDataCapture(y_byte);
}
}
That code is used to record raw file from audio recording. However, I want to change SAMPLING_RATE to 16000. Could I used same FFT_POINTS is 1024? If not, Please suggest to me how to compute it and MAGIC_SCALE. I tried to used that values but the sound appear noise. Thanks.
The reference link is here
The FFT algorithm doesn't care about the sampling rate. I know that sounds somewhat non-intuitive, but each sample of the output (referred to as a bin) represents the magnitude of the content that is (SAMPLING_FREQUENCY / FFT_POINTS) Hz wide.
MAGIC_SCALE is just a value to scale the data and doesn't have a real impact when you're dealing with doubles. If it were a DFFT using 16 bit integers, you'd have a scaling factor to ensure your input doesn't saturate/overflow during it's calculations.
Notice that the FFT function is never told what SAMPLING_FREQUENCY or MAGIC_SCALE is.
In the case of 44100, and 1024, each bin is the spectral content of ~43 Hz. In the case of 16000, it's ~15Hz.
If 44100 works and 16000 doesn't, the problem is probably in the code that manages your audioBuffer[] variable.
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.
Below is the code for my play() method which simply generates an arbitrary set of frequencies and blends them into one tone.
The problem is that it only plays for a split second - I need is to play it continuously. I would appreciate suggestions on how to constantly generate the sound using the AudioTrack class in Android. I believe it has something to do with the MODE_STREAM constant, but I can't quite work out how.
Here is the link to AudioTrack class documentation:
http://developer.android.com/reference/android/media/AudioTrack.html
EDIT: I forgot to mention one important aspect, it can't loop. Due to the mixing of sometimes up to 50+ frequencies, it will sound choppy because there is no least common denominator for all frequency peaks - or it's too far down the waveform to store as one sound.
/**
* play - begins playing the sound
*/
public void play() {
// Get array of frequencies with their relative strengths
double[][] soundData = getData();
// Track samples array
final double samples[] = new double[1024];
// Calculate the average sum in the array and write it to sample
for (int i = 0; i < samples.length; ++i) {
double valueSum = 0;
for (int j = 0; j < soundData.length; j++) {
valueSum += Math.sin(2 * Math.PI * i / (SAMPLE_RATE / soundData[j][0]));
}
samples[i] = valueSum / soundData.length;
}
// Obtain a minimum buffer size
int minBuffer = AudioTrack.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
if (minBuffer > 0) {
// Create an AudioTrack
mTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE, AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT, minBuffer, AudioTrack.MODE_STREAM);
// Begin playing track
mTrack.play();
// Fill the buffer
if (mBuffer.length < samples.length) {
mBuffer = new short[samples.length];
}
for (int k = 0; k < samples.length; k++) {
mBuffer[k] = (short) (samples[k] * Short.MAX_VALUE);
}
// Write audio data to track for real-time audio sythesis
mTrack.write(mBuffer, 0, samples.length);
}
// Once everything has successfully begun, indicate such.
isPlaying = true;
}
It looks like the code is almost there. It just needs a loop to keep generating the samples, putting them in the buffer, and writing them to the AudioTrack. Right now just one buffer full gets written before it exits which is why it stops so quickly.
void getSamples(double[] samples) {
// Get array of frequencies with their relative strengths
double[][] soundData = getData();
// Calculate the average sum in the array and write it to sample
for (int i = 0; i < samples.length; ++i) {
double valueSum = 0;
for (int j = 0; j < soundData.length; j++) {
valueSum += Math.sin(2 * Math.PI * i / (SAMPLE_RATE / soundData[j][0]));
}
samples[i] = valueSum / soundData.length;
}
}
public void endPlay() {
done = true;
}
/**
* play - begins playing the sound
*/
public void play() {
// Obtain a minimum buffer size
int minBuffer = AudioTrack.getMinBufferSize(SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
if (minBuffer > 0) {
// Create an AudioTrack
mTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE, AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT, minBuffer, AudioTrack.MODE_STREAM);
// Begin playing track
mTrack.play();
// Track samples array
final double samples[] = new double[1024];
while (!done) {
// Fill the buffer
if (mBuffer.length < samples.length) {
mBuffer = new short[samples.length];
}
getSamples(samples);
for (int k = 0; k < samples.length; k++) {
mBuffer[k] = (short) (samples[k] * Short.MAX_VALUE);
}
// Write audio data to track for real-time audio sythesis
mTrack.write(mBuffer, 0, samples.length);
// Once everything has successfully begun, indicate such.
isPlaying = true;
}
}
// Once everything is done, indicate such.
isPlaying = false;
}
I am using android platform, from the following reference question I come to know that using AudioRecord class which returns raw data I can filter range of audio frequency depends upon my need but for that I will need algorithm, can somebody please help me out to find algorithm to filter range b/w 14,400 bph and 16,200 bph.
I tried "JTransform" but i don't know can I achieve this with JTransform or not ? Currently I am using "jfftpack" to display visual effects which works very well but i can't achieve audio filter using this.
Reference here
help appreciated Thanks in advance.
Following is my code as i mentioned above i am using "jfftpack" library to display you may find this library reference in the code please don't get confuse with that
private class RecordAudio extends AsyncTask<Void, double[], Void> {
#Override
protected Void doInBackground(Void... params) {
try {
final AudioRecord audioRecord = findAudioRecord();
if(audioRecord == null){
return null;
}
final short[] buffer = new short[blockSize];
final double[] toTransform = new double[blockSize];
audioRecord.startRecording();
while (started) {
final int bufferReadResult = audioRecord.read(buffer, 0, blockSize);
for (int i = 0; i < blockSize && i < bufferReadResult; i++) {
toTransform[i] = (double) buffer[i] / 32768.0; // signed 16 bit
}
transformer.ft(toTransform);
publishProgress(toTransform);
}
audioRecord.stop();
audioRecord.release();
} catch (Throwable t) {
Log.e("AudioRecord", "Recording Failed");
}
return null;
/**
* #param toTransform
*/
protected void onProgressUpdate(double[]... toTransform) {
canvas.drawColor(Color.BLACK);
for (int i = 0; i < toTransform[0].length; i++) {
int x = i;
int downy = (int) (100 - (toTransform[0][i] * 10));
int upy = 100;
canvas.drawLine(x, downy, x, upy, paint);
}
imageView.invalidate();
}
There are a lot of tiny details in this process that can potentially hang you up here. This code isn't tested and I don't do audio filtering very often so you should be extremely suspicious here. This is the basic process you would take for filtering audio:
Get audio buffer
Possible audio buffer conversion (byte to float)
(optional) Apply windowing function i.e. Hanning
Take the FFT
Filter frequencies
Take inverse FFT
I'm assuming you have some basic knowledge of Android and audio recording so will cover steps 4-6 here.
//it is assumed that a float array audioBuffer exists with even length = to
//the capture size of your audio buffer
//The size of the FFT will be the size of your audioBuffer / 2
int FFT_SIZE = bufferSize / 2;
FloatFFT_1D mFFT = new FloatFFT_1D(FFT_SIZE); //this is a jTransforms type
//Take the FFT
mFFT.realForward(audioBuffer);
//The first 1/2 of audioBuffer now contains bins that represent the frequency
//of your wave, in a way. To get the actual frequency from the bin:
//frequency_of_bin = bin_index * sample_rate / FFT_SIZE
//assuming the length of audioBuffer is even, the real and imaginary parts will be
//stored as follows
//audioBuffer[2*k] = Re[k], 0<=k<n/2
//audioBuffer[2*k+1] = Im[k], 0<k<n/2
//Define the frequencies of interest
float freqMin = 14400;
float freqMax = 16200;
//Loop through the fft bins and filter frequencies
for(int fftBin = 0; fftBin < FFT_SIZE; fftBin++){
//Calculate the frequency of this bin assuming a sampling rate of 44,100 Hz
float frequency = (float)fftBin * 44100F / (float)FFT_SIZE;
//Now filter the audio, I'm assuming you wanted to keep the
//frequencies of interest rather than discard them.
if(frequency < freqMin || frequency > freqMax){
//Calculate the index where the real and imaginary parts are stored
int real = 2 * fftBin;
int imaginary = 2 * fftBin + 1;
//zero out this frequency
audioBuffer[real] = 0;
audioBuffer[imaginary] = 0;
}
}
//Take the inverse FFT to convert signal from frequency to time domain
mFFT.realInverse(audioBuffer, false);