diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CallVideoPanel.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CallVideoPanel.kt index e611d489..2475732a 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CallVideoPanel.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/CallVideoPanel.kt @@ -268,7 +268,8 @@ private class CallVideoTexture(context: Context, private val video: CallVideoEng val now = System.nanoTime(); commits++ if (updateAt != 0L) { commitMaxUs = maxOf(commitMaxUs, (now - updateAt) / 1000); updateAt = 0 } // Если timestamp ещё старый, новое TextureView update само назначит draw/commit. - video.presented(surface, texture.timestamp / 1000) + val matched = video.presented(surface, texture.timestamp / 1000) + if (!matched && (Build.VERSION.SDK_INT < 29 || !isHardwareAccelerated)) postOnAnimation { consumed(texture, surface) } if (reportAt == 0L) reportAt = now if (now - reportAt >= 1_000_000_000) { LogManager.addLog("DEBUG", "call", "video surface intervalMs=${(now - reportAt) / 1_000_000} " + diff --git a/tools/chatgui-android/audio-probe/src/main/java/com/utun/audioprobe/VideoSurfaceProbeActivity.java b/tools/chatgui-android/audio-probe/src/main/java/com/utun/audioprobe/VideoSurfaceProbeActivity.java index 7100dee5..13dcbcc9 100644 --- a/tools/chatgui-android/audio-probe/src/main/java/com/utun/audioprobe/VideoSurfaceProbeActivity.java +++ b/tools/chatgui-android/audio-probe/src/main/java/com/utun/audioprobe/VideoSurfaceProbeActivity.java @@ -38,7 +38,7 @@ public final class VideoSurfaceProbeActivity extends Activity implements Texture private volatile boolean cancelled; private volatile long expectedPts = -1, submittedAt; private final AtomicInteger submitted = new AtomicInteger(); - private int acknowledged, updates, mismatches, lastReported; + private int acknowledged, updates, staleChecks, lastReported; private int fps; private boolean legacy; private long started, ackMaxUs; @@ -50,10 +50,11 @@ public final class VideoSurfaceProbeActivity extends Activity implements Texture getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); producer.start(); render = new Handler(producer.getLooper()); view = new TextureView(this); view.setSurfaceTextureListener(this); setContentView(view); - Log.i(TAG, "start legacy=" + legacy + " fps=" + fps + " hardware=" + getWindow().getAttributes().flags); + Log.i(TAG, "start legacy=" + legacy + " fps=" + fps); } @Override public void onSurfaceTextureAvailable(SurfaceTexture next, int width, int height) { + Log.i(TAG, "surface available hardware=" + view.isHardwareAccelerated() + " view=" + width + "x" + height); texture = next; next.setDefaultBufferSize(640, 360); surface = new Surface(next); observer = view.getViewTreeObserver(); observer.addOnPreDrawListener(this); render.post(() -> { @@ -89,7 +90,12 @@ public final class VideoSurfaceProbeActivity extends Activity implements Texture private void committed() { if (cancelled || texture == null || expectedPts < 0) return; long pts = texture.getTimestamp() / 1000; - if (pts != expectedPts) { mismatches++; Log.w(TAG, "timestamp actual=" + pts + " expected=" + expectedPts); return; } + // Посторонний UI draw может завершиться до прихода следующего буфера. Это не ACK: + // ожидаем TextureView update, который сам создаст следующий draw/commit. + if (pts != expectedPts) { + if (++staleChecks == 1) Log.d(TAG, "defer ACK timestamp=" + pts + " expected=" + expectedPts); + return; + } expectedPts = -1; acknowledged++; ackMaxUs = Math.max(ackMaxUs, (System.nanoTime() - submittedAt) / 1000); long next = started + submitted.get() * 1_000_000_000L / fps; @@ -125,14 +131,19 @@ public final class VideoSurfaceProbeActivity extends Activity implements Texture + " advanced=" + (acknowledged - lastReported) + " updates=" + updates + " ackMaxUs=" + ackMaxUs); lastReported = acknowledged; if (elapsed >= 8_000_000_000L) { - boolean valid = legacy ? acknowledged < 30 : acknowledged >= fps * 8 * 0.8 && mismatches == 0; + boolean valid = legacy ? acknowledged < 30 : acknowledged >= fps * 8 * 0.8 + && acknowledged <= submitted.get() && submitted.get() - acknowledged <= 1; Log.i(TAG, "RESULT " + (valid ? "PASS" : "FAIL") + " legacy=" + legacy + " targetFps=" + fps + " submitted=" + submitted.get() - + " acknowledged=" + acknowledged + " updates=" + updates + " mismatches=" + mismatches + " ackMaxUs=" + ackMaxUs); + + " acknowledged=" + acknowledged + " updates=" + updates + " staleChecks=" + staleChecks + " ackMaxUs=" + ackMaxUs); finish(); return; } view.invalidate(); main.postDelayed(this::tick, 500); } - @Override protected void onStop() { cancelled = true; super.onStop(); } + @Override protected void onStop() { + cancelled = true; + if (texture == null) { Log.i(TAG, "stopped before surface became available"); producer.quitSafely(); } + super.onStop(); + } private static void check(boolean condition, String stage) { if (!condition) throw new IllegalStateException(stage + " EGL error=" + EGL14.eglGetError()); }