GC が原因でアプリケーションのパフォーマンスに問題があり、何が起こっているのかを理解する経験があまりありません。詳しい状況はこちら。
STFT(簡単な説明)を適用して、オーディオをリアルタイムで処理するアプリケーションを構築しようとしています。
基本的に、各バッファーを取得し (私の場合、最小バッファー サイズは 1148 バイトです)、ウィンドウ関数を適用して、フレームのマトリックスを取得します。次に、各フレームで FFT を適用します。最後に、周波数と時間に基づいて、各フレームにゲインを適用できます。次に、バッファーの変更されたバージョンを取得するためにパスを戻します。
私のサンプリング周波数は 8000Hz なので、各バッファーは 1148/8000 = 144ms 未満で処理を行う必要があります。使ってSystem.currentTimeMillis()
みたところ、バッファの処理に通常70~100ミリ秒かかるとのことで、問題ありませんでした。
しかし、問題はガベージ コレクターにあります。下のスクリーンショットからわかるように、メモリがいっぱいのようです。GCアクションにより、オーディオが時々割れます。
問題は、次の 2 つの点に気付いたことです。
FFT と IFFT を実行せず、フレームをそのままにしておくと、CG_CONCURRENT メッセージは表示されません。これは、FFT が大量のデータ (複素数の配列) を生成するためです。
すべての操作は別のスレッドで行われます。そのため、アプリケーションを起動した直後に DDMS の観点から手動で CG を作成しようとしました。以下のコードからわかるように、onCreate メソッドでは、アプリケーションはレイアウトのみをロードします。ヒープ ダンプを見て、Leaks Suspect レポートを生成しました。ほとんどのメモリは、クラス 'android.content.res.Resources' と 'android.graphics.Bitmap' によって占有されています.. (スクリーンショットはこちら)
提案はありますか?私のメモリが最初にすでに90%使用されているのは奇妙だと思います..また、私のニーズを満たすためにヒープを少し増やすことはできません
コード
MainActivity.java
package com.example.fileoutjava;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.View;
public class MainActivity extends Activity {
static final int BUFFER_FACTOR = 1;
DataInputStream dis;
static final int FREQ = 8000;
static final int FRAME_LENGHT = 32;
static final int FRAME_SHIFT = 16;
boolean isMusicStopped = true;
AudioTrack at;
Thread playThread;
long time;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void playMusic(View v) {
if (at == null) {
Log.d("PLAY MUSIC", "LAUNCHING NEW PLAYER");
playThread = new Thread(musicPlayerThread);
playThread.start();
}
}
public void stopMusic(View v) {
isMusicStopped = true;
playThread = null;
}
Runnable musicPlayerThread = new Runnable() {
public void run() {
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
/* eg: 8000 bytes per second, 1000 bytes = 125 ms */
InputStream is = null;
DataInputStream dis = null;
try {
is = MainActivity.this.getApplicationContext().getAssets().open("test.wav");
} catch (IOException e) {
e.printStackTrace();
}
if (is!=null)
dis = new DataInputStream(is); //dis = new DataInputStream(new BufferedInputStream(is,bSize));
isMusicStopped = false;
int min_bSize = AudioTrack.getMinBufferSize(FREQ, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
int bSize = min_bSize*BUFFER_FACTOR;
STFT stft = new STFT(FRAME_SHIFT,FRAME_LENGHT,FREQ,bSize);
at = new AudioTrack(AudioManager.STREAM_MUSIC, FREQ, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, bSize, AudioTrack.MODE_STREAM);
at.play();
int count = 0;
byte[] buffer = new byte[bSize];
time = System.currentTimeMillis();
try {
while (!isMusicStopped && (count = dis.read(buffer, 0, bSize)) >= 0) {
Log.d("TIME ELAPSED", ""+(System.currentTimeMillis()-time));
time = System.currentTimeMillis();
//Windowing
stft.frameBuffer(buffer);
//fourier transform and inverse
stft.fourierAnalysis();
// Overlapp-Add
stft.buildBuffer(buffer);
at.write(buffer, 0, count);
}
if (at != null) {
at.stop();
at.flush();
at.release();
at = null;
}
if (dis != null) {
dis.close();
dis = null;
}
if (is != null) {
is.close();
is = null;
}
if (stft != null) stft = null;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
private void stop() {
isMusicStopped = true;
playThread = null;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
protected void onPause() {
this.stop();
super.onPause();
}
@Override
protected void onDestroy() {
this.stop();
super.onDestroy();
}
}
STFT.java
package com.example.fileoutjava;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import android.util.Log;
import com.badlogic.gdx.audio.analysis.FFT;
public class STFT {
private int fs, fl; //frame shift and frame length in ms
private int n_fs, n_fl; //frame shift and length in samples
private int buf_len; //length of the buffer array (bytes)
private int data_len; //length of the buffer array (converted to short)
private int padded_data_len; //put 0 padding before and after the buffer short[] data
private float n_segs; //number of frames that can be taken from one buffer array
private float[][] stft_matrix;
private float[] window; //Hamming coefficient
private float norm_factor = 0;
private boolean search_norm_factor = true;
private FFT fft;
private int i,j,k; //index for loops
private ByteBuffer bb;
private float[] tmp_buf;
private float[] tmp_fft;
private float[] tmp_ifft;
public STFT(int frame_shift, int frame_length, int freq, int buf_len) {
fs = frame_shift;
fl = frame_length;
this.buf_len = buf_len;
this.data_len = buf_len/2;
//compute values from ms to samples
n_fs = (int) Math.floor(fs*freq/1000);
n_fl = (int) Math.floor(fl*freq/1000);
padded_data_len = 2*n_fl + data_len;
//create coefficients
window = hamming(n_fl);
tmp_buf = new float[padded_data_len];
bb = ByteBuffer.allocateDirect(2);
bb.order(ByteOrder.LITTLE_ENDIAN);
//compute how many frames can be extracted from the buffer
n_segs = 1 + (float) (Math.ceil((this.padded_data_len-n_fl)/n_fs));
//data matrix: size of frame (with padding from previous frame) * number of segments
stft_matrix = new float[n_fl][(int)n_segs];
Log.d("STFT STATS", "BufLen:"+this.buf_len+" // Flen:"+n_fl+" // FSh:"+n_fs+
" // Nsegs:"+n_segs);
//Initialize the FFT object
fft = new FFT(n_fl*2,freq);
//buffers for FFT data, with zero padding
tmp_fft= new float[n_fl*2];
tmp_ifft = new float[n_fl];
for (int i=0; i<n_fl*2; i++) {
tmp_fft[i] = 0;
tmp_ifft[i/2] = 0;
}
}
//frames the whole buffer into the stft matrix
public void frameBuffer(byte[] buf) {
//initialize tmp_buffer and add 0 padding
for (k=0; k<padded_data_len; k++)
tmp_buf[k] = 0;
//fill the short[] buffer converting from byte[] buffer
for (i=0; i<buf_len; i+=2) {
bb.position(0);
bb.put(buf[i]);
bb.put(buf[i+1]);
tmp_buf[n_fl+i/2] = (float) bb.getShort(0);
}
//frame the short[] buffer into the matrix using windowing
for (j=0; j<n_segs; j++) {
for (int i=0; i<n_fl; i++) {
stft_matrix[i][j] = tmp_buf[j*n_fs+i]*window[i];
//NORMALIZATION FACTOR RETRIEVAL: only the first time
if (search_norm_factor && (j*n_fs+i) == 512)
norm_factor+=window[i];
}
}
if (search_norm_factor)
norm_factor *= 1.2;
//retrieve the norm factor only the first time
search_norm_factor = false;
}
//sums all frames from STFT matrix into one buffer
public void buildBuffer(byte[] output) {
//initialize tmp_buffer and add 0 padding
for (k=0; k<padded_data_len; k++)
tmp_buf[k] = 0;
//Overlap-Add
for (j=0; j<n_segs; j++) {
for (i=0; i<n_fl; i++) {
tmp_buf[j*n_fs+i] += stft_matrix[i][j];
}
}
//convert from short[] to byte[] (with normalization)
for (i=0; i<buf_len; i+=2) {
bb.position(0);
bb.putShort( (short) (tmp_buf[n_fl+i/2]/norm_factor) );
output[i] = bb.get(0);
output[i+1] = bb.get(1);
}
}
//FFT and IFFT of the buffer
public void fourierAnalysis() {
for (j=0; j<n_segs;j++) {
for (i=0; i<n_fl; i++) {
tmp_fft[i] = stft_matrix[i][j];
}
fft.forward(tmp_fft);
//OPERATIONS ON THE SPECTRUM ?
fft.inverse(tmp_ifft);
for (int i=0; i<n_fl; i++) {
stft_matrix[i][j] = tmp_ifft[i];
}
}
}
//utility method for Hamming coefficients
private float[] hamming(int len){
float[] win = new float[len];
for (i=0; i<len; i++){
win[i] = (float) (0.54-0.46*Math.cos((2*Math.PI*i)/(len-1)));
}
return win;
}
}