Android基于Agora.io的屏幕共享:ImageReader方案可行性验证
关于Agora.io屏幕共享ImageReader方案的问题解答
首先明确:完全可以用ImageReader实现Agora屏幕共享,你的思路方向是对的,只是代码里几个关键细节没处理到位,导致画面无法正常显示,不用必须依赖官方Demo里的EglCore等底层渲染方案。
下面针对你的代码问题和优化方向逐一说明:
1. 核心问题:ImageFormat格式不兼容
你创建ImageReader时用了ImageFormatType.Jpeg,但Agora SDK的外部视频源默认不支持直接处理JPEG格式——JPEG是压缩后的图像格式,需要解码为YUV才能被SDK识别。这是导致画面无法显示的最关键原因。
解决方法:改用Android屏幕捕获原生支持的YUV格式ImageFormatType.Yuv420_888,这也是Agora SDK兼容的标准视频格式。
2. VideoFrame参数配置错误
你的AgoraVideoFrame参数存在多处不匹配:
- Format字段:Android的
ImageFormat枚举和Agora的VIDEO_PIXEL_FORMAT枚举不直接对应,比如Yuv420_888需要转换成Agora的VIDEO_PIXEL_FORMAT_NV21或VIDEO_PIXEL_FORMAT_I420。 - Stride与分量处理:YUV420_888包含Y、U、V三个Plane,你只取了第一个Plane的PixelStride作为整体Stride是错误的,需要分别处理三个分量并转换为Agora支持的格式(比如NV21)。
- TimeStamp单位:Agora期望的时间戳是微秒,而
DateTime.UtcNow.Ticks是100纳秒单位,需要转换为(long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMicroseconds。 - 遗漏Width参数:你的代码里没给
AgoraVideoFrame设置Width,SDK无法正确识别画面尺寸。
3. 其他优化点
- ImageReader缓存数量:你设置了
maxImages = 1,这会导致系统缓存不足频繁丢帧,建议改为2-3。 - VirtualDisplay Flags:
DisplayFlags.SupportsProtectedBuffers非必要时可以去掉,部分设备可能不兼容该参数导致捕获异常。
修正后的关键代码片段
1. 修改ShareScreenImageAvailableListener构造与OnImageAvailable逻辑
public class ShareScreenImageAvailableListener : Java.Lang.Object, ImageReader.IOnImageAvailableListener { private ScreenShareService _screenShareService; private ImageReader _imageReader; public Surface Surface { get { return _imageReader.Surface; } } public ShareScreenImageAvailableListener(ScreenShareService screenShareService) { _screenShareService = screenShareService; // 改用YUV420_888格式,增加缓存数量到2 _imageReader = ImageReader.NewInstance(_screenShareService.Width, _screenShareService.Height, Android.Graphics.ImageFormatType.Yuv420888, 2); _imageReader.SetOnImageAvailableListener(this, _screenShareService.Handler); } public void OnImageAvailable(ImageReader reader) { using (var image = reader.AcquireLatestImage()) { if (image == null) return; var planes = image.GetPlanes(); var yPlane = planes[0]; var uPlane = planes[1]; var vPlane = planes[2]; var yBuffer = yPlane.Buffer; var uBuffer = uPlane.Buffer; var vBuffer = vPlane.Buffer; int ySize = yBuffer.Remaining(); int uSize = uBuffer.Remaining(); int vSize = vBuffer.Remaining(); // 将YUV420_888转换为Agora支持的NV21格式 byte[] nv21 = new byte[ySize + uSize + vSize]; yBuffer.Get(nv21, 0, ySize); vBuffer.Get(nv21, ySize, vSize); uBuffer.Get(nv21, ySize + vSize, uSize); _screenShareService.ShareScreenImage(nv21, image.Height, AgoraVideoFrame.VIDEO_PIXEL_FORMAT_NV21, yPlane.RowStride); image.Close(); } } }
2. 修改ShareScreenImage方法
internal void ShareScreenImage(byte[] bytes, int height, int agoraFormat, int stride) { var agoraVideoFrame = new AgoraVideoFrame() { Buf = bytes, Height = height, Width = _screenShareService.Width, // 补充Width参数 Format = agoraFormat, Stride = stride, // 转换为Agora要求的微秒时间戳 TimeStamp = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMicroseconds, CropBottom = 0, CropLeft = 0, CropRight = 0, CropTop = 0, Rotation = 0 // 可根据设备方向调整,比如竖屏场景设为90 }; AgoraVideoService.Instance.ShareExternalVideoFrame(agoraVideoFrame); }
额外注意事项
- 权限验证:确保已申请
android.permission.RECORD_AUDIO、android.permission.CAPTURE_VIDEO_OUTPUT(Android 10+),并完成屏幕捕获的动态权限申请。 - 线程安全:确保
PushExternalVideoFrame调用在Agora SDK允许的线程中执行,可通过Handler切换到指定线程。 - 数据验证:可以先将捕获到的YUV数据保存为本地文件,验证数据是否正确,再推送给Agora。
内容的提问来源于stack exchange,提问作者Jakub Holovsky
相关产品推荐
相关产品推荐

