ugu*_*gur 6 multithreading android surfaceview tarsosdsp
我正在使用TarsosDSP实时计算音高.它使用AudioDispatcher实现Runnable并通过handlePitch方法发布结果以在主线程中使用.
我正在使用SurfaceView在更新时绘制此值.SurfaceView还需要另一个线程在画布上绘制.所以我有2个可运行的对象.我无法管理如何通过一个线程更新表面视图,同时从另一个线程(audiodispatcher)获取音高值.
我只想使用我在handlePitch()方法中获得的分值来更新我在surfaceview上的绘图.但我的应用冻结了.任何的想法?
在MainAcitivity.java中(onCreate(...))
myView = (MySurfaceView) findViewById(R.id.myview);
int sr = 44100;//The sample rate
int bs = 2048;
AudioDispatcher d = AudioDispatcherFactory.fromDefaultMicrophone(sr,bs,0);
PitchDetectionHandler printPitch = new PitchDetectionHandler() {
@Override
public void handlePitch(final PitchDetectionResult pitchDetectionResult, AudioEvent audioEvent) {
final float p = pitchDetectionResult.getPitch();
runOnUiThread(new Runnable() {
@Override
public void run() {
if (p != -1){
float cent = (float) (1200*Math.log(p/8.176)/Math.log(2)) % 12;
System.out.println(cent);
myView.setCent(cent);
}
}
});
}
};
PitchProcessor.PitchEstimationAlgorithm algo = PitchProcessor.PitchEstimationAlgorithm.YIN; //use YIN
AudioProcessor pitchEstimator = new PitchProcessor(algo, sr,bs,printPitch);
d.addAudioProcessor(pitchEstimator);
d.run();//starts the dispatching process
AudioProcessor p = new PitchProcessor(algo, sr, bs, printPitch);
d.addAudioProcessor(p);
new Thread(d,"Audio Dispatcher").start();
Run Code Online (Sandbox Code Playgroud)
在SurfaceView.java中(下面的代码是从构造函数触发的)
myThread = new MyThread(this);
surfaceHolder = getHolder();
bmpIcon = BitmapFactory.decodeResource(getResources(),
R.mipmap.ic_launcher);
iconWidth = bmpIcon.getWidth();
iconHeight = bmpIcon.getHeight();
density = getResources().getDisplayMetrics().scaledDensity;
setLabelTextSize(Math.round(DEFAULT_LABEL_TEXT_SIZE_DP * density));
surfaceHolder.addCallback(new SurfaceHolder.Callback(){
@Override
public void surfaceCreated(SurfaceHolder holder) {
myThread.setRunning(true);
myThread.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
// TODO Auto-generated method stub
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
myThread.setRunning(false);
while (retry) {
try {
myThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}});
protected void drawSomething(Canvas canvas) {
updateCanvas(canvas, this.cent); //draws some lines depending on the cent value
}
public void setCent(double cent) {
if (this.cent > maxCent)
this.cent = maxCent;
this.cent = cent;
}
Run Code Online (Sandbox Code Playgroud)
更新:
MyThread.java
public class MyThread extends Thread {
MySurfaceView myView;
private boolean running = false;
public MyThread(MySurfaceView view) {
myView = view;
}
public void setRunning(boolean run) {
running = run;
}
@Override
public void run() {
while(running){
Canvas canvas = myView.getHolder().lockCanvas();
if(canvas != null){
synchronized (myView.getHolder()) {
myView.drawSomething(canvas);
}
myView.getHolder().unlockCanvasAndPost(canvas);
}
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果我正确理解您的问题,您有一个在其自己的线程()上工作的独立事件源PitchDetectionHandler,并且SurfaceView当来自源的事件到来时,您希望在其自己的线程上重新绘制事件源。如果是这样的话,那么我认为整个想法sleep(1000)都是错误的。你应该跟踪实际事件并对它们做出反应,而不是睡觉等待它们。似乎在 Android 上最简单的解决方案是使用HandlerThread//基础设施Looper,Handler如下所示:
注意以下代码中的错误;我不仅没有尝试过,甚至还没有编译过。
import android.graphics.Canvas;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.view.SurfaceHolder;
public class SurfacePitchDrawingHelper implements Handler.Callback, SurfaceHolder.Callback2 {
private static final int MSG_DRAW = 100;
private static final int MSG_FORCE_REDRAW = 101;
private final Object _lock = new Object();
private SurfaceHolder _surfaceHolder;
private HandlerThread _drawingThread;
private Handler _handler;
private float _lastDrawnCent;
private volatile float _lastCent;
private final boolean _processOnlyLast = true;
@Override
public void surfaceCreated(SurfaceHolder holder) {
synchronized (_lock) {
_surfaceHolder = holder;
_drawingThread = new HandlerThread("SurfaceDrawingThread") {
@Override
protected void onLooperPrepared() {
super.onLooperPrepared();
}
};
_drawingThread.start();
_handler = new Handler(_drawingThread.getLooper(), this); // <-- this is where bug was
_lastDrawnCent = Float.NaN;
//postForceRedraw(); // if needed
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
synchronized (_lock) {
// clean queue and kill looper
_handler.removeCallbacksAndMessages(null);
_drawingThread.getLooper().quit();
while (true) {
try {
_drawingThread.join();
break;
} catch (InterruptedException e) {
}
}
_handler = null;
_drawingThread = null;
_surfaceHolder = null;
}
}
@Override
public void surfaceRedrawNeeded(SurfaceHolder holder) {
postForceRedraw();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
synchronized (_lock) {
_surfaceHolder = holder;
}
postForceRedraw();
}
private void postForceRedraw() {
_handler.sendEmptyMessage(MSG_FORCE_REDRAW);
}
public void postRedraw(float cent) {
if (_processOnlyLast) {
_lastCent = cent;
_handler.sendEmptyMessage(MSG_DRAW);
} else {
Message message = _handler.obtainMessage(MSG_DRAW);
message.obj = Float.valueOf(cent);
_handler.sendMessage(message);
}
}
private void doRedraw(Canvas canvas, float cent) {
// put actual painting logic here
}
@Override
public boolean handleMessage(Message msg) {
float lastCent = _processOnlyLast ? _lastCent : ((Float) msg.obj).floatValue();
boolean shouldRedraw = (MSG_FORCE_REDRAW == msg.what)
|| ((MSG_DRAW == msg.what) && (_lastDrawnCent != lastCent));
if (shouldRedraw) {
Canvas canvas = null;
synchronized (_lock) {
if (_surfaceHolder != null)
canvas =_surfaceHolder.lockCanvas();
}
if (canvas != null) {
doRedraw(canvas, lastCent);
_surfaceHolder.unlockCanvasAndPost(canvas);
_lastDrawnCent = lastCent;
}
return true;
}
return false;
}
}
Run Code Online (Sandbox Code Playgroud)
然后在你的活动课上你做类似的事情
private SurfaceView surfaceView;
private SurfacePitchDrawingHelper surfacePitchDrawingHelper = new SurfacePitchDrawingHelper();
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
surfaceView.getHolder().addCallback(surfacePitchDrawingHelper);
int sr = 44100;//The sample rate
int bs = 2048;
AudioDispatcher d = AudioDispatcherFactory.fromDefaultMicrophone(sr, bs, 0);
PitchDetectionHandler printPitch = new PitchDetectionHandler() {
@Override
public void handlePitch(final PitchDetectionResult pitchDetectionResult, AudioEvent audioEvent) {
final float p = pitchDetectionResult.getPitch();
float cent = (float) (1200 * Math.log(p / 8.176) / Math.log(2)) % 12;
System.out.println(cent);
surfacePitchDrawingHelper.postRedraw(cent);
}
};
PitchProcessor.PitchEstimationAlgorithm algo = PitchProcessor.PitchEstimationAlgorithm.YIN; //use YIN
AudioProcessor pitchEstimator = new PitchProcessor(algo, sr, bs, printPitch);
d.addAudioProcessor(pitchEstimator);
// d.run();//starts the dispatching process <-- this was another bug in the original code (see update)!
AudioProcessor p = new PitchProcessor(algo, sr, bs, printPitch);
d.addAudioProcessor(p);
new Thread(d, "Audio Dispatcher").start();
...
}
Run Code Online (Sandbox Code Playgroud)
请注意,它SurfacePitchDrawingHelper封装了与绘图相关的大部分逻辑,并且您的子类中不需要MySurfaceView(无论如何我认为这是一个坏主意)。
主要思想是当新的被创建时,SurfacePitchDrawingHelper创建是专用的。+ +提供了一个有用的基础设施,可以在一个单独的线程上运行(以有效的方式)无限循环,该线程等待传入消息并一一处理它们。因此,其有效的公共 API 还包含单个方法,可用于要求绘图线程进行另一次重绘,这正是 custom 所使用的。“询问”是通过将消息放入队列中以由绘图线程(更具体地说是我们对该线程的自定义)处理来完成的。我没有费心将真正的公共 API 减少为“有效”的 API,因为这会使代码变得更加复杂,而且我太懒了。当然,这两个“实现”都可以转移到内部类中。HandlerThreadSurfaceHandlerThreadLooperHandlerSurfaceHolder.Callback2postRedrawPitchDetectionHandlerHandler
您需要做出一个重要的决定:绘制线程是否应该cent按照到达的顺序生成每条入站消息(所有值),或者仅生成绘制发生时的最新消息。如果PitchDetectionHandler生成事件的速度比“绘图线程”更新的速度快得多,这可能会变得尤其重要Surface。我相信在大多数情况下,只处理最后一个值就可以了,PitchDetectionHandler但我在代码中保留了两个版本以供说明。这种区别目前是在代码中按_processOnlyLast字段实现的。最有可能的是,您应该做出这个决定,并删除这个几乎恒定的字段和不相关分支中的代码。
当然,不要忘记将实际的绘图逻辑放入其中doRedraw
更新(为什么后退按钮不起作用)
TLDR 版本
违规行是
d.run();//starts the dispatching process
Run Code Online (Sandbox Code Playgroud)
直接评论出来吧!
加长版
查看您的示例,我们可以看到它d是AudioDispatcher哪个implements Runnable,因此runmethod 是在新线程上调用的方法。您可能会注意到这很重要,因为在这个方法内部会执行一些 IO 并阻止它运行的线程。所以在你的情况下它阻塞了主 UI 线程。你做的几行
new Thread(d, "Audio Dispatcher").start();
Run Code Online (Sandbox Code Playgroud)
这似乎是正确的使用方法AudioDispatcher
这可以从我在评论中询问的堆栈跟踪中轻松看出。