【发布时间】:2019-09-04 12:14:15
【问题描述】:
我想在后台线程上无限循环运行图像分类器。该函数应在启动应用程序后立即调用。我想为分类器提供来自同时在 UI 线程中播放的预录视频的当前帧,所以后台线程应该告诉 UI 线程,一旦完成,我可以用当前帧提供它并重新运行分类器。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private VideoView videoView;
private ImageView imageView;
private Uri uri_video;
private MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
private MediaController mMediaController;
private static volatile int currentPosition;
private static volatile Bitmap mBitmap;
private final Object lock = new Object();
private volatile boolean runClassifier = false;
private HandlerThread backgroundThread;
private Handler backgroundHandler;
private static final String HANDLE_THREAD_NAME = "ClassifierBackground";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = findViewById(R.id.imageView);
uri_video = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.kim);
mediaMetadataRetriever = new MediaMetadataRetriever();
mediaMetadataRetriever.setDataSource(getApplication(), uri_video);
videoView = findViewById(R.id.videoView);
videoView.setVideoURI(uri_video);
mMediaController = new MediaController(this);
videoView.setMediaController(mMediaController);
videoView.setOnPreparedListener(MyVideoViewPreparedListener);
videoView.start();
startBackgroundThread();
}
/** Starts a background thread and its {@link Handler}. */
private void startBackgroundThread() {
backgroundThread = new HandlerThread(HANDLE_THREAD_NAME);
backgroundThread.start();
backgroundHandler = new Handler(backgroundThread.getLooper());
synchronized (lock) {
runClassifier = true;
}
backgroundHandler.post(periodicClassify);
}
/** Stops the background thread and its {@link Handler}. */
private void stopBackgroundThread() {
backgroundThread.quitSafely();
try {
backgroundThread.join();
backgroundThread = null;
backgroundHandler = null;
synchronized (lock) {
runClassifier = false;
}
} catch (InterruptedException e) {
Log.e(TAG, "Interrupted when stopping background thread", e);
}
}
private Runnable periodicClassify =
new Runnable() {
@Override
public void run() {
synchronized (lock) {
if (runClassifier) {
// classifyFrame(); // This will be implemented later
Log.d(TAG, "run: Classifier is running");
SystemClock.sleep(100); // Instead I simulate the classifier via sleep
}
}
runOnUiThread(new Runnable() {
@Override
public void run() {
setImageViewToCurrentFrame();
}
});
backgroundHandler.post(periodicClassify);
}
};
private void setImageViewToCurrentFrame(){
currentPosition = videoView.getCurrentPosition(); //in millisecond
mBitmap = mediaMetadataRetriever
.getFrameAtTime(currentPosition * 1000); //unit in microsecond
imageView.setImageBitmap(mBitmap);
}
MediaPlayer.OnPreparedListener MyVideoViewPreparedListener =
new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
long duration = videoView.getDuration(); //in millisecond
Toast.makeText(MainActivity.this,
"Duration: " + duration + " (ms)",
Toast.LENGTH_LONG).show();
setImageViewToCurrentFrame();
}
};
@Override
protected void onDestroy() {
super.onDestroy();
stopBackgroundThread();
}
EDIT1:
我从these videos 得到了一些关于如何做到这一点的粗略想法。似乎我需要一个具有 backgroundHandler (Handler) 与 UI 线程通信的 backgroundThread (HandlerThread) 和一个 Looper 以保持后台线程处于活动状态。 setImageViewToCurrentFrame 使用 videoView.getCurrentPosition() 更新 mBitmap。
但是与分类器的运行时间(SystemClock.sleep(100) 需要 100 毫秒)相比,更新非常慢(>10 秒)。
EDIT2: 问题似乎是 ImageView 的性能似乎更新得很慢。用 TextView 替换它,使后台线程和 UI 线程保持同步。我现在会寻找除 ImageView 之外的其他解决方案
【问题讨论】: