Add hardware TextureView ACK regression probe
This commit is contained in:
1 parent
65f7babb2a
commit
3eccf58c5e
2 files changed
+140
No files matched your search
@@ -2,6 +2,7 @@
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<application android:label="uTun audio probe" android:theme="@android:style/Theme.Material.Light.NoActionBar" android:allowBackup="false">
|
||||
<activity android:name=".VideoSurfaceProbeActivity" android:exported="true" android:configChanges="orientation|screenSize" />
|
||||
<activity android:name=".ProbeActivity" android:exported="true" android:configChanges="orientation|screenSize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package com.utun.audioprobe;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.opengl.EGL14;
|
||||
import android.opengl.EGLConfig;
|
||||
import android.opengl.EGLContext;
|
||||
import android.opengl.EGLDisplay;
|
||||
import android.opengl.EGLExt;
|
||||
import android.opengl.EGLSurface;
|
||||
import android.opengl.GLES20;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
import android.view.TextureView;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.WindowManager;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/** Настоящий TextureView/EGL, один кадр до ACK. Без камеры, сети и основного приложения.
|
||||
* am start -n com.utun.audioprobe/.VideoSurfaceProbeActivity --ei fps 60 --ez legacy false
|
||||
* legacy регистрирует commit во время draw; fixed — в preDraw, как видеозвонок.
|
||||
* Постороннее UI-обновление каждые 500мс воспроизводит зависимость от статистики звонка. */
|
||||
public final class VideoSurfaceProbeActivity extends Activity implements TextureView.SurfaceTextureListener, ViewTreeObserver.OnPreDrawListener {
|
||||
private static final String TAG = "VideoSurfaceProbe";
|
||||
private final Handler main = new Handler();
|
||||
private final HandlerThread producer = new HandlerThread("video-surface-probe");
|
||||
private Handler render;
|
||||
private TextureView view;
|
||||
private ViewTreeObserver observer;
|
||||
private EGLDisplay display = EGL14.EGL_NO_DISPLAY;
|
||||
private EGLContext context = EGL14.EGL_NO_CONTEXT;
|
||||
private EGLSurface window = EGL14.EGL_NO_SURFACE;
|
||||
private Surface surface;
|
||||
private SurfaceTexture 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 fps;
|
||||
private boolean legacy;
|
||||
private long started, ackMaxUs;
|
||||
|
||||
@Override public void onCreate(Bundle saved) {
|
||||
super.onCreate(saved);
|
||||
fps = getIntent().getIntExtra("fps", 60); legacy = getIntent().getBooleanExtra("legacy", false);
|
||||
if (fps != 30 && fps != 60) { Log.e(TAG, "Unsupported fps=" + fps); finish(); return; }
|
||||
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);
|
||||
}
|
||||
|
||||
@Override public void onSurfaceTextureAvailable(SurfaceTexture next, int width, int height) {
|
||||
texture = next; next.setDefaultBufferSize(640, 360); surface = new Surface(next);
|
||||
observer = view.getViewTreeObserver(); observer.addOnPreDrawListener(this);
|
||||
render.post(() -> {
|
||||
try {
|
||||
display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
|
||||
int[] version = new int[2]; check(EGL14.eglInitialize(display, version, 0, version, 1), "initialize");
|
||||
int[] attributes = {EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT,
|
||||
EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8, EGL14.EGL_NONE};
|
||||
EGLConfig[] config = new EGLConfig[1]; int[] count = new int[1];
|
||||
check(EGL14.eglChooseConfig(display, attributes, 0, config, 0, 1, count, 0) && count[0] == 1, "config");
|
||||
context = EGL14.eglCreateContext(display, config[0], EGL14.EGL_NO_CONTEXT,
|
||||
new int[]{EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE}, 0);
|
||||
check(context != EGL14.EGL_NO_CONTEXT, "context");
|
||||
window = EGL14.eglCreateWindowSurface(display, config[0], surface, new int[]{EGL14.EGL_NONE}, 0);
|
||||
check(window != EGL14.EGL_NO_SURFACE && EGL14.eglMakeCurrent(display, window, window, context), "surface");
|
||||
started = System.nanoTime(); main.post(this::tick); send();
|
||||
} catch (Exception e) { Log.e(TAG, "producer failed", e); main.post(this::finish); }
|
||||
});
|
||||
}
|
||||
|
||||
private void send() {
|
||||
if (cancelled) return;
|
||||
try {
|
||||
int number = submitted.get(); long pts = 1_000_000L + number * 1_000_000L / fps;
|
||||
GLES20.glClearColor((number % 3) / 2f, ((number + 1) % 3) / 2f, ((number + 2) % 3) / 2f, 1);
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
||||
expectedPts = pts; submittedAt = System.nanoTime(); submitted.incrementAndGet();
|
||||
check(EGLExt.eglPresentationTimeANDROID(display, window, pts * 1000), "timestamp");
|
||||
check(EGL14.eglSwapBuffers(display, window), "swap");
|
||||
} catch (Exception e) { Log.e(TAG, "frame failed", e); main.post(this::finish); }
|
||||
}
|
||||
|
||||
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; }
|
||||
expectedPts = -1; acknowledged++;
|
||||
ackMaxUs = Math.max(ackMaxUs, (System.nanoTime() - submittedAt) / 1000);
|
||||
long next = started + submitted.get() * 1_000_000_000L / fps;
|
||||
render.postDelayed(this::send, Math.max(0, (next - System.nanoTime() + 999_999) / 1_000_000));
|
||||
}
|
||||
|
||||
@Override public boolean onPreDraw() {
|
||||
if (!cancelled && !legacy) view.getViewTreeObserver().registerFrameCommitCallback(this::committed);
|
||||
return true;
|
||||
}
|
||||
@Override public void onSurfaceTextureUpdated(SurfaceTexture source) {
|
||||
updates++;
|
||||
if (legacy) view.getViewTreeObserver().registerFrameCommitCallback(this::committed);
|
||||
}
|
||||
@Override public void onSurfaceTextureSizeChanged(SurfaceTexture source, int width, int height) {}
|
||||
@Override public boolean onSurfaceTextureDestroyed(SurfaceTexture source) {
|
||||
if (observer != null && observer.isAlive()) observer.removeOnPreDrawListener(this);
|
||||
observer = null; cancelled = true;
|
||||
render.post(() -> {
|
||||
EGL14.eglMakeCurrent(display, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
|
||||
if (window != EGL14.EGL_NO_SURFACE) EGL14.eglDestroySurface(display, window);
|
||||
if (context != EGL14.EGL_NO_CONTEXT) EGL14.eglDestroyContext(display, context);
|
||||
if (display != EGL14.EGL_NO_DISPLAY) EGL14.eglTerminate(display);
|
||||
surface.release(); source.release(); producer.quitSafely();
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
private void tick() {
|
||||
if (cancelled) return;
|
||||
long elapsed = System.nanoTime() - started;
|
||||
Log.i(TAG, "progress legacy=" + legacy + " fps=" + fps + " elapsedMs=" + elapsed / 1_000_000 + " acknowledged=" + acknowledged
|
||||
+ " 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;
|
||||
Log.i(TAG, "RESULT " + (valid ? "PASS" : "FAIL") + " legacy=" + legacy + " targetFps=" + fps + " submitted=" + submitted.get()
|
||||
+ " acknowledged=" + acknowledged + " updates=" + updates + " mismatches=" + mismatches + " ackMaxUs=" + ackMaxUs);
|
||||
finish(); return;
|
||||
}
|
||||
view.invalidate(); main.postDelayed(this::tick, 500);
|
||||
}
|
||||
@Override protected void onStop() { cancelled = true; super.onStop(); }
|
||||
private static void check(boolean condition, String stage) {
|
||||
if (!condition) throw new IllegalStateException(stage + " EGL error=" + EGL14.eglGetError());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user