gpt4 book ai didi

android - TarsosDSP 和 SurfaceView 多线程问题

转载 作者:塔克拉玛干 更新时间:2023-11-02 21:14:24 30 4
gpt4 key购买 nike

我正在使用 TarsosDSP 实时计算音调频率。它使用实现 Runnable 的 AudioDispatcher 并通过 handlePitch 方法发布结果以在主线程中使用。

我正在使用 SurfaceView 在更新时绘制此值。 SurfaceView 还需要另一个线程才能在 Canvas 上绘制。所以我有 2 个可运行的对象。我无法管理如何通过一个线程更新表面 View ,同时从另一个线程(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();

在 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;
}

更新:

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();
}

}
}

最佳答案

如果我正确理解你的问题,你有一个独立的事件源在它自己的线程 (PitchDetectionHandler) 和一个你想重新绘制的 SurfaceView 上工作当来自源的事件到来时,它自己的线程。如果是这种情况,那么我认为 sleep(1000) 的整个想法是错误的。您应该跟踪实际事件并对它们使用react,而不是睡着等待它们。在 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;
}
}

然后在你的 Activity 课上你做类似的事情

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();

...

}

请注意,SurfacePitchDrawingHelper 封装了大部分与绘图相关的逻辑,在您的子类 MySurfaceView 中没有必要(我认为这是个坏主意)。

主要思想是 SurfacePitchDrawingHelper 在创建新的 Surface 时创建专用的 HandlerThreadHandlerThread + Looper + Handler 提供了一个有用的基础架构,可以在等待传入消息的单独线程上运行(以高效方式)无限循环并一一处理。因此,除了 SurfaceHolder.Callback2 之外,它的有效公共(public) API 由单个 postRedraw 方法组成,可用于要求绘图线程进行另一次重绘,这正是自定义使用的方法PitchDetectionHandler。 “询问”是通过将消息放入队列中以供绘图线程处理(更具体地说是我们在该线程上自定义的 Handler)来完成的。我没有费心将真正的公共(public) API 减少为“有效”API,因为它使代码变得有点复杂而且我太懒了。但当然,这两个“实现”都可以移至内部类。

您需要做出一个重要的决定:绘图线程是应该按到达的顺序生成每个入站消息(所有 cent 值),还是仅在绘图发生时生成最新消息。如果 PitchDetectionHandler 生成事件的速度比“绘图线程”可以更新 Surface 的速度快得多,这可能会变得尤为重要。我相信在大多数情况下,只处理 PitchDetectionHandler 中的最后一个值是可以的,但我在代码中保留了两个版本以供说明。这种区别目前在代码中通过 _processOnlyLast 字段实现。很可能你应该做出这个决定,只是摆脱这个几乎恒定的字段和不相关分支中的代码。

当然不要忘记将您的实际绘图逻辑放入 doRedraw


更新(为什么后退按钮不起作用)

TLDR 版本

违规行是

 d.run();//starts the dispatching process

就把它注释掉吧!

更长的版本

查看您的示例,我们可以看到 dAudioDispatcher,它 实现 Runnable,因此 run 方法是在新线程上调用的方法。您可能会注意到这很重要,因为在该方法内部执行一些 IO 并阻塞它运行的线程。因此,在您的情况下,它阻塞了主 UI 线程。你做的几行

new Thread(d, "Audio Dispatcher").start();

这似乎是使用 AudioDispatcher 的正确方法

这可以从我在评论中要求的堆栈跟踪中轻松看出。

Stack trace of the main thread

关于android - TarsosDSP 和 SurfaceView 多线程问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42589373/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com