如何在展示H264解码内容的Android SurfaceView上绘制矩形
Android SurfaceView叠加绘制矩形解决方案
你可以根据自身业务需求选择以下两种实现方案,均不会干扰原有H264解码渲染逻辑:
方案1:叠加透明覆盖层(开发成本最低)
该方案通过在SurfaceView上层叠加一个同尺寸的透明自定义View专门绘制矩形,无需修改原有视频渲染逻辑,适配性最强:
- 布局调整:将原有SurfaceView和自定义透明View放在同一个FrameLayout容器内,保证二者尺寸完全对齐
- 自定义透明View需要做如下配置:
- 初始化时调用
setZOrderOnTop(true)保证View层级在SurfaceView之上,同时设置背景为透明色 - 内部新增成员变量存储矩形坐标、宽高参数,以及绘制矩形用的Paint实例
- 对外暴露更新矩形参数的方法,参数更新后调用
invalidate()触发重绘 - 重写
onDraw()方法,参数合法时调用canvas.drawRect()绘制矩形
- 初始化时调用
- Socket接收矩形参数的逻辑中,收到新参数后需切换到主线程调用自定义View的参数更新方法,避免子线程刷新UI报错
- 如果Socket传输的矩形参数是基于原始视频分辨率的坐标,需要先根据SurfaceView实际显示尺寸做比例换算再传入
方案2:直接在渲染画布绘制(性能最优)
该方案直接在视频渲染的同一画布上绘制矩形,无额外View的过度绘制开销,适合对性能要求高的场景:
- 找到原有H264解码后渲染帧的逻辑位置,在视频帧渲染完成后、提交画布之前插入矩形绘制逻辑
- 对矩形参数的读写加线程同步锁,避免Socket线程更新参数到一半时被渲染线程读取,出现坐标错乱
- 直接使用渲染视频帧的同一个Canvas实例绘制矩形,绘制完成后再正常调用
unlockCanvasAndPost()提交画布即可
坐标换算逻辑(两种方案通用)
如果Socket传入的矩形坐标基于原始视频分辨率,需要先做比例适配再绘制:
// 原始视频分辨率 int videoWidth = 1920; int videoHeight = 1080; // SurfaceView实际显示尺寸 int surfaceWidth = videoSurface.getWidth(); int surfaceHeight = videoSurface.getHeight(); // 比例换算 float scaleX = (float) surfaceWidth / videoWidth; float scaleY = (float) surfaceHeight / videoHeight; float realX = socketX * scaleX; float realY = socketY * scaleY; float realWidth = socketWidth * scaleX; float realHeight = socketHeight * scaleY;
核心示例代码
自定义矩形覆盖层View
public class RectOverlayView extends View { private final Paint rectPaint; private RectF currentRect = null; public RectOverlayView(Context context) { super(context); init(); } public RectOverlayView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { rectPaint = new Paint(); rectPaint.setColor(Color.RED); rectPaint.setStyle(Paint.Style.STROKE); rectPaint.setStrokeWidth(4f); rectPaint.setAntiAlias(true); // 配置透明背景和上层显示权限 setBackgroundColor(Color.TRANSPARENT); setZOrderOnTop(true); } // 外部更新矩形参数,需在主线程调用 public void updateRect(float x, float y, float width, float height) { currentRect = new RectF(x, y, x + width, y + height); invalidate(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (currentRect != null) { canvas.drawRect(currentRect, rectPaint); } } }
布局示例
<FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> <SurfaceView android:id="@+id/video_surface" android:layout_width="match_parent" android:layout_height="match_parent"/> <com.yourpackage.RectOverlayView android:id="@+id/rect_overlay" android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout>
Socket参数更新示例
// Socket子线程收到矩形参数后切换到主线程更新 runOnUiThread(() -> { // 可先做坐标换算再传入 rectOverlay.updateRect(realX, realY, realWidth, realHeight); });
内容的提问来源于stack exchange,提问作者ayhanunal
相关产品推荐
相关产品推荐

