webrtc截取图像

该博客围绕WebRTC P2P音视频功能的截图展开。介绍了原理,指出视频流源头是摄像头、终点是屏幕,每一帧数据需调用并通过渲染器绘制。给出保存第一帧图像至本地的代码实现,还提到截图后数据改变渲染器可能无法解析的问题及创建帧数据备份的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、原理介绍

对于webrtc p2p音视频功能,不管是本端视频流还是对端视频流,视频流的源头都是摄像头,终点是都是屏幕(不同的终端需要不同的控件支持)。明白了这一点,每一帧数据需要调用VideoRenderer,然后通过渲染器在控件上进行绘制。
VideoRenderer中定义了如下的接口:

public static interface Callbacks {
    void renderFrame(org.webrtc.VideoRenderer.I420Frame i420Frame);
}

每一帧数据刷新时,会回调该接口。

二、代码实现

private static void copyPlane(ByteBuffer src, ByteBuffer dst) {
    src.position(0).limit(src.capacity());
    dst.put(src);
    dst.position(0).limit(dst.capacity());
}

public static android.graphics.YuvImage ConvertTo(org.webrtc.VideoRenderer.I420Frame src, int imageFormat) {
    byte[] bytes = new byte[src.yuvStrides[0] * src.height +
            src.yuvStrides[1] * src.height / 2 +
            src.yuvStrides[2] * src.height / 2];
    int[] strides = new int[3];
    switch (imageFormat) {
        default:
            return null;
        case android.graphics.ImageFormat.YV12: {
            ByteBuffer tmp = ByteBuffer.wrap(bytes, 0, src.yuvStrides[0] * src.height);
            copyPlane(src.yuvPlanes[0], tmp);
            tmp = ByteBuffer.wrap(bytes, src.yuvStrides[0] * src.height, src.yuvStrides[2] * src.height / 2);
            copyPlane(src.yuvPlanes[2], tmp);
            tmp = ByteBuffer.wrap(bytes, src.yuvStrides[0] * src.height + src.yuvStrides[2] * src.height / 2, src.yuvStrides[1] * src.height / 2);
            copyPlane(src.yuvPlanes[1], tmp);
            strides[0] = src.yuvStrides[0];
            strides[1] = src.yuvStrides[2];
            strides[2] = src.yuvStrides[1];
            return new YuvImage(bytes, imageFormat, src.width, src.height, strides);
        }

        case android.graphics.ImageFormat.NV21: {
            if (src.yuvStrides[0] != src.width)
                return null;
            if (src.yuvStrides[1] != src.width / 2)
                return null;
            if (src.yuvStrides[2] != src.width / 2)
                return null;

            ByteBuffer tmp = ByteBuffer.wrap(bytes, 0, src.width * src.height);
            copyPlane(src.yuvPlanes[0], tmp);

            byte[] tmparray = new byte[src.width / 2 * src.height / 2];
            tmp = ByteBuffer.wrap(tmparray, 0, src.width / 2 * src.height / 2);

            copyPlane(src.yuvPlanes[2], tmp);
            for (int row = 0; row < src.height / 2; row++) {
                for (int col = 0; col < src.width / 2; col++) {
                    bytes[src.width * src.height + row * src.width + col * 2] = tmparray[row * src.width / 2 + col];
                }
            }
            copyPlane(src.yuvPlanes[1], tmp);
            for (int row = 0; row < src.height / 2; row++) {
                for (int col = 0; col < src.width / 2; col++) {
                    bytes[src.width * src.height + row * src.width + col * 2 + 1] = tmparray[row * src.width / 2 + col];
                }
            }
            return new YuvImage(bytes, imageFormat, src.width, src.height, null);
        }
    }
}

private static class ProxyRenderer implements VideoRenderer.Callbacks {
    private VideoRenderer.Callbacks target;

    @Override
    synchronized public void renderFrame(VideoRenderer.I420Frame frame) {
        if (target == null) {
            Logging.d(TAG, "Dropping frame in proxy because target is null.");
            VideoRenderer.renderFrameDone(frame);
            return;
        }

        // 查看帧数据
        Logging.d(TAG, "height = " + frame.height
                + " width = " + frame.width
                + " rotationDegree = " + frame.rotationDegree
                + " textureId = " + frame.textureId
                + " rotatedHeight = " + frame.rotatedHeight()
                + " rotatedWidth = " + frame.rotatedWidth());

        // 保存数据
        android.graphics.YuvImage yuvImage = ConvertTo(frame, ImageFormat.NV21);
        java.io.File newFile = new File("/storage/emulated/0/1/webrtc_1");
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(newFile);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        yuvImage.compressToJpeg(new Rect(0, 0, yuvImage.getWidth(), yuvImage.getHeight()), 100, fileOutputStream);

        target.renderFrame(frame);
    }

    synchronized public void setTarget(VideoRenderer.Callbacks target) {
        this.target = target;
    }
}

上面代码的主要功能是保存webrtc的第一帧图像至本地。

三、注意事项

截图图像后frame数据会被改变,渲染器可能会无法解析frame数据。
解决方案:创建一份帧数据备份

VideoRenderer.I420Frame frame2 =  new VideoRenderer.I420Frame(frame.width, frame.height, frame.rotationDegree, frame.yuvStrides, frame.yuvPlanes, NULL);

参考链接:
https://www.jianshu.com/p/5902d4953ed9
https://blog.youkuaiyun.com/weixin_38372482/article/details/80817274
https://www.jianshu.com/p/1513e51e043d

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值