背景
我们在一些测试时延的场景需要知道某一帧画面对应的RTP的时间戳,比如云应用的触控指令到画面变动被打包成RTP包的整一段时延,我们称为云测时延。知道这个信息有利于我们进行时延的分段分析
关键逻辑
底层WebRTC抛出VideoFrame信息的时候携带RTP的信息,比如RTP的时间戳(同一帧画面分包的场景,不同的RTP的时间戳也是相同的),交付到应用层的时候将时间戳通过OpenGL ES渲染出来
核心代码
渲染的逻辑:
public class VideoFrameDrawer {
public static final String TAG = "VideoFrameDrawer";
// 创建一个文字的纹理类
private TextOverlayRenderer textOverlayRenderer;
private volatile String overlayText = null;
public void setOverlayText(String text) {
this.overlayText = text;
}
public void clearOverlayText() {
this.overlayText = null;
}
public void drawFrame(VideoFrame frame, RendererCommon.GlDrawer drawer,
@Nullable Matrix additionalRenderMatrix, int viewportX, int viewportY, int viewportWidth,
int viewportHeight) {
...... // 此处省略了很多渲染的逻辑
drawer.drawYuv(yuvUploader.getYuvTextures(),
RendererCommon.convertMatrixFromAndroidGraphicsMatrix(renderMatrix), renderWidth,
renderHeight, viewportX, viewportY, viewportWidth, viewportHeight);
}
// 这里是更新的逻辑,在更新YUV的时候,同步将这一帧RTP的时间戳也更新出来
if(textOverlayRenderer == null) {
textOverlayRenderer = new TextOverlayRenderer();
}
if (overlayText != null) {
textOverlayRenderer.updateText(overlayText);
textOverlayRenderer.draw();
}
文字纹理代码:
package org.webrtc;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
public class TextOverlayRenderer {
private int textTextureId = -1;
private int programId;
private int positionHandle;
private int texCoordHandle;
private int textureHandle;
// 简单的全屏顶点着色器
private static final String VERTEX_SHADER =
"attribute vec4 aPosition;\n" +
"attribute vec2 aTexCoord;\n" +
"varying vec2 vTexCoord;\n" +
"void main() {\n" +
" gl_Position = aPosition;\n" +
" vTexCoord = aTexCoord;\n" +
"}";
// 采样2D纹理的片段着色器
private static final String FRAGMENT_SHADER =
"precision mediump float;\n" +
"uniform sampler2D uTexture;\n" +
"varying vec2 vTexCoord;\n" +
"void main() {\n" +
" gl_FragColor = texture2D(uTexture, vTexCoord);\n" +
"}";
// 顶点坐标 (左下角矩形)
private static final float[] VERTICES = {
-1.0f, -1.0f,
-0.5f, -1.0f,
-1.0f, -0.8f,
-0.5f, -0.8f,
};
// 纹理坐标
private static final float[] TEX_COORDS = {
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
};
private FloatBuffer vertexBuffer;
private FloatBuffer texCoordBuffer;
public TextOverlayRenderer() {
vertexBuffer = ByteBuffer.allocateDirect(VERTICES.length * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
vertexBuffer.put(VERTICES).position(0);
texCoordBuffer = ByteBuffer.allocateDirect(TEX_COORDS.length * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
texCoordBuffer.put(TEX_COORDS).position(0);
programId = GlUtil.createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
positionHandle = GLES20.glGetAttribLocation(programId, "aPosition");
texCoordHandle = GLES20.glGetAttribLocation(programId, "aTexCoord");
textureHandle = GLES20.glGetUniformLocation(programId, "uTexture");
}
/**
* 生成带文字的纹理
*/
public void updateText(String text) {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setTextSize(48);
paint.setColor(Color.WHITE);
int width = (int) paint.measureText(text);
int height = 64;
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawText(text, 0, height - 10, paint);
if (textTextureId == -1) {
int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
textTextureId = textures[0];
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textTextureId);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
}
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textTextureId);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
}
/**
* 绘制文字纹理
*/
public void draw() {
if (textTextureId == -1) return;
GLES20.glUseProgram(programId);
GLES20.glEnableVertexAttribArray(positionHandle);
GLES20.glVertexAttribPointer(positionHandle, 2, GLES20.GL_FLOAT, false, 0, vertexBuffer);
GLES20.glEnableVertexAttribArray(texCoordHandle);
GLES20.glVertexAttribPointer(texCoordHandle, 2, GLES20.GL_FLOAT, false, 0, texCoordBuffer);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textTextureId);
GLES20.glUniform1i(textureHandle, 0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GLES20.glDisableVertexAttribArray(positionHandle);
GLES20.glDisableVertexAttribArray(texCoordHandle);
}
}
调用更新逻辑:
frameDrawer.setOverlayText("rtp: " +frame.getRtpTimestamp());
这里的frame是VideoFrame,安卓在渲染帧的时候会使用该类,所以在这个类上面做了拓展,增加了一个私有的rtp时间戳字段。