You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 18:12:37