为什么美颜插件比传统集成方式更快?

news2024/11/19 3:36:57

都说云市场插件快,快在哪里呢?

美颜功能是实时互动应用的基础功能,广泛应用在社交、直播、约会、会议等场景,开发者们往往在集成美颜功能时候非常头疼。今天,我们来介绍云市场美颜插件相比传统的裸数据集成方式快在哪里。

上手快

每一个插件都有属于自己的示例代码和使用文档,集成插件之前先跑通示例代码,能够帮助开发者们快速看到美颜效果,并快速熟悉插件集成到自己的项目中需要哪些工作。

下载插件、下载资源包、填写 appId、配置好测试证书。

点点gradle、点点run app,全部搞定。

在这里插入图片描述

在这里插入图片描述

集成快

我们以集成相芯美颜为例:

传统的裸数据集成:

  • 首先需要将大段的 Camera 代码加入相关工程中

  • 从 Camera 回调接口取出每一帧图像数据

  • 传给相芯进行图像处理

  • 把处理完的数据传回声网的 video pipeline 中,进行编码并发送

这样项目中需要集成大量代码,而且需要开发者需要大量音视频知识和实践经验,比如:相芯需要处理何种格式的视频数据、Android 的 texture 处理等等。集成完成后,还要调试音视频体验相关问题,比如:回音、啸叫、音画不同步、黑屏、卡顿、crash、性能、机型适配等等。

在这里插入图片描述

今天,声网作为实时互动领域的领军者,我们把集成的最佳工程实践提供了出来,云市场上的相芯美颜插件把以上这 1000 件事情都解决了,大大降低了时间精力,3 个 API 就可以简洁地完成集成:

  • addExtension()
  • enableExtension()
  • setExtensionProperty()

完成后就能在声网视频流中轻松使用到插件效果,还可以再接入其他更多扩展能力,比如:实时变声、实时翻译、内容审核等等。

这样极大减少了实时互动开发者的工作量,整体的集成代码的管理和后续维护会变得非常干净简洁,并且节约了大量测试、调试的时间,让开发者把精力放在业务逻辑和用户体验上。

还是没概念?不如直接看看相芯美颜的集成案例吧:

第一个为裸数据集成方案,第二个为相芯美颜插件集成方案:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

    private void initVideoView() {
        mBinding.cbFaceBeautify.setOnCheckedChangeListener((buttonView, isChecked) -> {
            if (iBeautyFaceUnity == null) {
                return;
            }
            iBeautyFaceUnity.setFaceBeautifyEnable(isChecked);
        });
        mBinding.cbMakeup.setOnCheckedChangeListener((buttonView, isChecked) -> {
            if (iBeautyFaceUnity == null) {
                return;
            }
            iBeautyFaceUnity.setMakeUpEnable(isChecked);
        });
        mBinding.cbSticker.setOnCheckedChangeListener((buttonView, isChecked) -> {
            if (iBeautyFaceUnity == null) {
                return;
            }
            iBeautyFaceUnity.setStickerEnable(isChecked);
        });
        mBinding.cbBodyBeauty.setOnCheckedChangeListener((buttonView, isChecked) -> {
            if (iBeautyFaceUnity == null) {
                return;
            }
            iBeautyFaceUnity.setBodyBeautifyEnable(isChecked);
        });
        mBinding.ivCamera.setOnClickListener(v -> {
            rtcEngine.switchCamera();
            isFrontCamera = !isFrontCamera;
        });
        mBinding.tvBeautyInput.setText(isSingleInput ? R.string.beauty_input_single : R.string.beauty_input_double);
        mBinding.tvBeautyInput.setOnClickListener(v -> {
            isSingleInput = !isSingleInput;
            mBinding.tvBeautyInput.setText(isSingleInput ? R.string.beauty_input_single : R.string.beauty_input_double);
        });
        mBinding.smallVideoContainer.setOnClickListener(v -> updateVideoLayouts(!FaceUnityBeauty.this.isLocalFull));
    }

    private void initRtcEngine() {
        try {
            mRtcEngineEventHandler = new IRtcEngineEventHandler() {
                @Override
                public void onError(int err) {
                    super.onError(err);
                    showLongToast(String.format(Locale.US, "msg:%s, code:%d", RtcEngine.getErrorDescription(err), err));
                }

                @Override
                public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
                    super.onJoinChannelSuccess(channel, uid, elapsed);
                    mLocalVideoLayout.setReportUid(uid);
                }

                @Override
                public void onUserJoined(int uid, int elapsed) {
                    super.onUserJoined(uid, elapsed);
                    runOnUIThread(() -> {
                        if (mRemoteVideoLayout == null) {
                            mRemoteVideoLayout = new VideoReportLayout(requireContext());
                            mRemoteVideoLayout.setReportUid(uid);
                            TextureView videoView = new TextureView(requireContext());
                            rtcEngine.setupRemoteVideo(new VideoCanvas(videoView, Constants.RENDER_MODE_HIDDEN, uid));
                            mRemoteVideoLayout.addView(videoView);
                            updateVideoLayouts(isLocalFull);
                        }
                    });
                }

                @Override
                public void onUserOffline(int uid, int reason) {
                    super.onUserOffline(uid, reason);
                    runOnUIThread(() -> {
                        if (mRemoteVideoLayout != null && mRemoteVideoLayout.getReportUid() == uid) {
                            mRemoteVideoLayout.removeAllViews();
                            mRemoteVideoLayout = null;
                            updateVideoLayouts(isLocalFull);
                        }
                    });
                }

                @Override
                public void onLocalAudioStats(LocalAudioStats stats) {
                    super.onLocalAudioStats(stats);
                    runOnUIThread(() -> mLocalVideoLayout.setLocalAudioStats(stats));
                }

                @Override
                public void onLocalVideoStats(Constants.VideoSourceType source, LocalVideoStats stats) {
                    super.onLocalVideoStats(source, stats);
                    runOnUIThread(() -> mLocalVideoLayout.setLocalVideoStats(stats));
                }

                @Override
                public void onRemoteAudioStats(RemoteAudioStats stats) {
                    super.onRemoteAudioStats(stats);
                    if (mRemoteVideoLayout != null) {
                        runOnUIThread(() -> mRemoteVideoLayout.setRemoteAudioStats(stats));
                    }
                }

                @Override
                public void onRemoteVideoStats(RemoteVideoStats stats) {
                    super.onRemoteVideoStats(stats);
                    if (mRemoteVideoLayout != null) {
                        runOnUIThread(() -> mRemoteVideoLayout.setRemoteVideoStats(stats));
                    }
                }
            };
            rtcEngine = RtcEngine.create(getContext(), getString(R.string.agora_app_id), mRtcEngineEventHandler);

            if (rtcEngine == null) {
                return;
            }


            mVideoFrameObserver = new IVideoFrameObserver() {
                @Override
                public boolean onCaptureVideoFrame(VideoFrame videoFrame) {
                    if (isDestroyed) {
                        return true;
                    }
                    VideoFrame.Buffer buffer = videoFrame.getBuffer();
                    if (!(buffer instanceof VideoFrame.TextureBuffer)) {
                        return true;
                    }

                    VideoFrame.TextureBuffer texBuffer = (VideoFrame.TextureBuffer) buffer;

                    if (mTextureBufferHelper == null) {
                        doOnBeautyCreatingBegin();
                        mTextureBufferHelper = TextureBufferHelper.create("STRender", texBuffer.getEglBaseContext());
                        mTextureBufferHelper.invoke(() -> {
                            iBeautyFaceUnity = IBeautyFaceUnity.create(getContext());
                            return null;
                        });
                        doOnBeautyCreatingEnd();
                    }

                    VideoFrame.TextureBuffer processBuffer;
                    if (isSingleInput) {
                        processBuffer = processSingleInput(texBuffer);
                    } else {
                        processBuffer = processDoubleInput(texBuffer);
                    }
                    if(processBuffer == null){
                        return true;
                    }
                    // drag one frame to avoid reframe when switching camera.
                    if(mFrameRotation != videoFrame.getRotation()){
                        mFrameRotation = videoFrame.getRotation();
                        return false;
                    }
                    videoFrame.replaceBuffer(processBuffer, mFrameRotation, videoFrame.getTimestampNs());
                    return true;
                }

                @Override
                public boolean onPreEncodeVideoFrame(VideoFrame videoFrame) {
                    return false;
                }

                @Override
                public boolean onScreenCaptureVideoFrame(VideoFrame videoFrame) {
                    return false;
                }

                @Override
                public boolean onPreEncodeScreenVideoFrame(VideoFrame videoFrame) {
                    return false;
                }

                @Override
                public boolean onMediaPlayerVideoFrame(VideoFrame videoFrame, int mediaPlayerId) {
                    return false;
                }

                @Override
                public boolean onRenderVideoFrame(String channelId, int uid, VideoFrame videoFrame) {
                    return false;
                }

                @Override
                public int getVideoFrameProcessMode() {
                    return IVideoFrameObserver.PROCESS_MODE_READ_WRITE;
                }

                @Override
                public int getVideoFormatPreference() {
                    return IVideoFrameObserver.VIDEO_PIXEL_DEFAULT;
                }

                @Override
                public boolean getRotationApplied() {
                    return false;
                }

                @Override
                public boolean getMirrorApplied() {
                    return false;
                }

                @Override
                public int getObservedFramePosition() {
                    return IVideoFrameObserver.POSITION_POST_CAPTURER;
                }
            };
            rtcEngine.registerVideoFrameObserver(mVideoFrameObserver);
            rtcEngine.enableVideo();
            rtcEngine.disableAudio();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private VideoFrame.TextureBuffer processSingleInput(VideoFrame.TextureBuffer texBuffer) {

        int width = texBuffer.getWidth();
        int height = texBuffer.getHeight();

        Integer processTexId = mTextureBufferHelper.invoke(() -> iBeautyFaceUnity.process(
                texBuffer.getTextureId(),
                width, height
        ));

        return mTextureBufferHelper.wrapTextureBuffer(
                width, height, VideoFrame.TextureBuffer.Type.RGB, processTexId,
                texBuffer.getTransformMatrix());
    }

    private VideoFrame.TextureBuffer processDoubleInput(VideoFrame.TextureBuffer texBuffer) {

        int textureId = texBuffer.getTextureId();
        int width = texBuffer.getWidth();
        int height = texBuffer.getHeight();

        int nv21Size = (int) (width * height * 3.0f / 2.0f + 0.5f);
        if (nv21ByteBuffer == null || nv21ByteBuffer.capacity() != nv21Size) {
            if (nv21ByteBuffer != null) {
                nv21ByteBuffer.clear();
            }
            nv21ByteBuffer = ByteBuffer.allocateDirect(nv21Size);
            nv21ByteArray = new byte[nv21Size];
        }


        VideoFrame.I420Buffer i420Buffer = texBuffer.toI420();
        YuvHelper.I420ToNV12(i420Buffer.getDataY(), i420Buffer.getStrideY(),
                i420Buffer.getDataV(), i420Buffer.getStrideV(),
                i420Buffer.getDataU(), i420Buffer.getStrideU(),
                nv21ByteBuffer, width, height);
        nv21ByteBuffer.position(0);
        nv21ByteBuffer.get(nv21ByteArray);
        i420Buffer.release();

        Integer processTexId = mTextureBufferHelper.invoke(() -> iBeautyFaceUnity.process(
                nv21ByteArray,
                textureId,
                width, height
        ));

        return mTextureBufferHelper.wrapTextureBuffer(
                width, height, VideoFrame.TextureBuffer.Type.RGB, processTexId, texBuffer.getTransformMatrix());
    }

    private void joinChannel() {
        int uid = new Random(System.currentTimeMillis()).nextInt(1000) + 10000;
        ChannelMediaOptions options = new ChannelMediaOptions();
        options.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING;
        options.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
        TokenUtils.gen(requireActivity(), channelId, uid, token -> {
            int ret = rtcEngine.joinChannel(token, channelId, uid, options);
            if (ret != Constants.ERR_OK) {
                showAlert(String.format(Locale.US, "%s\ncode:%d", RtcEngine.getErrorDescription(ret), ret));
            }
        });

        mLocalVideoLayout = new VideoReportLayout(requireContext());
        TextureView videoView = new TextureView(requireContext());
        rtcEngine.setupLocalVideo(new VideoCanvas(videoView, Constants.RENDER_MODE_HIDDEN));
        mLocalVideoLayout.addView(videoView);
        rtcEngine.startPreview();

        updateVideoLayouts(isLocalFull);
    }

    private void updateVideoLayouts(boolean isLocalFull) {
        this.isLocalFull = isLocalFull;
        mBinding.fullVideoContainer.removeAllViews();
        mBinding.smallVideoContainer.removeAllViews();
        if (isLocalFull) {
            if (mLocalVideoLayout != null) {
                mBinding.fullVideoContainer.addView(mLocalVideoLayout);
            }

            if (mRemoteVideoLayout != null) {
                mRemoteVideoLayout.getChildAt(0).setOnClickListener(v -> updateVideoLayouts(!FaceUnityBeauty.this.isLocalFull));
                mBinding.smallVideoContainer.addView(mRemoteVideoLayout);
            }
        } else {
            if (mLocalVideoLayout != null) {
                mLocalVideoLayout.getChildAt(0).setOnClickListener(v -> updateVideoLayouts(!FaceUnityBeauty.this.isLocalFull));
                mBinding.smallVideoContainer.addView(mLocalVideoLayout);
            }
            if (mRemoteVideoLayout != null) {
                mBinding.fullVideoContainer.addView(mRemoteVideoLayout);
            }
        }
    }

    private void doOnBeautyCreatingBegin() {
        Log.d(TAG, "doOnBeautyCreatingBegin...");
    }

    private void doOnBeautyCreatingEnd() {
        Log.d(TAG, "doOnBeautyCreatingEnd.");
        runOnUIThread(() -> {
            mBinding.cbBodyBeauty.setChecked(false);
            mBinding.cbFaceBeautify.setChecked(false);
            mBinding.cbSticker.setChecked(false);
            mBinding.cbMakeup.setChecked(false);
        });
    }

    private void doOnBeautyReleasingBegin() {
        Log.d(TAG, "doOnBeautyReleasingBegin...");
    }

    private void doOnBeautyReleasingEnd() {
        Log.d(TAG, "doOnBeautyReleasingEnd.");
    }
}

在这里插入图片描述
在这里插入图片描述

切换快

当产品和研发在技术选型、测试集成和后续运维中,经常会遇到当前供应商不满足业务要求,要换供应商的情况。但是又需要花时间去调研、阅读技术文档、沟通对接、议价等流程,非常繁琐。我们都为你考虑到了,我们的云市场插件在 wrapper 层做了标准化的封装,让您轻松实现同类型供应商、同客户端平台切换。

在这里插入图片描述
每一款插件都对接口进行了标准化的封装处理,即使切换了其他厂家,调用插件依旧是3个步骤:

  • addExtensions()
  • enableExtensions()
  • setExtensionProperty()

当然了,每一个插件都有自己的说明文档,任何细节都不放过。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

服务快

集成遇到问题?

没关系,声网服务线会出手!

我们有专业的服务线来解决您在集成插件时遇到的问题。任何疑难杂症,我们会和伙伴联合服务,24 * 7 为你保驾护航。

体验过的都说好。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/641474.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity与原生交互之Unity篇——AndroidStudio导出aar/jar包供Unity使用实现交互全流程(2)

序言:此篇介绍在(1)的基础上引入Unity的API库进行交互,原生调Unity 1.导入Unity库 注意: (1)老版本Unity的classes.jar里包含UnityPlayerActivity API (2)新版本Unity的UnityPlayerActivity API 在UnityEditor安装路径中,需单独导入,后续介绍 1.1导入unity的classes.jar …

Seesion会话超时时间测试-业务安全测试实操(3)

Seesion会话超时时间测试, Cookie仿冒测试, 密文比对认证测试 本地加密传输测试-业务安全测试实操(2)_luozhonghua2000的博客-CSDN博客 测试原理和方法 在用户成功登录系统获得Session认证会话后,该Session认证会话应具有生命周期,即用户在成功登录系统后,如果在固定时间内…

反汇编逆向实战——扫雷辅助制作

一、编程前准备 刚开始是预备知识,如果熟悉的话,可以直接跳到第二部分阅读 在 Windows API 中,SetTimer 函数用于创建一个定时器,并在指定的时间间隔后触发一个定时器消息。以下是关于 SetTimer 函数的介绍: 功能&a…

接口文档设计注意事项

接口名称清晰 一般接口url要求能看得出接口的作用。比如说,查询用户信息(queryUserInfo),就是一个不错的接口名称。 接口地址完整 接口的地址,也叫接口的URL地址。即别人调用你的接口,用的是什么URL。比…

【Unity Shader】入门到惊叹(1)基本概念:什么是网格?材质?Shader?

文章目录 一、什么是网格(Mesh)?二、什么是MeshFilter(网格过滤器)?三、什么是MeshRenderer(网格渲染器)?四、什么是材质(Material)?五、什么是Shader(着色器)?一、什么是网格(Mesh)? 如图,模型的三角形面就叫做网格(Mesh),它的本质是一堆顶点数据的规则排…

华为OD机试真题 JavaScript 实现【按身高和体重排队】【2022Q4 100分】,附详细解题思路

一、题目描述 某学校举行运动会,学生们按编号(1、2、3…n)进行标识,现需要按照身高由低到高排列,对身高相同的人,按体重由轻到重排列; 对于身高体重都相同的人,维持原有的编号顺序关系。请输出排列后的学生…

医械围城的觉醒时刻:从“乱世枭雄” 到“剩者为王”

我们现在看到医疗器械行业其实非常的热,不管是投资,还是创业,还有各种跨界进来打劫想分一杯羹的。 但是这个行业是不是一个围城? 真的是进来就可以捡钱吗? 在一片繁荣的景象下,企业的发展存在什么风险&a…

STM32FreeRTOS操作系统移植

移植好的FreeRTOS模板: 链接:https://pan.baidu.com/s/1_87VQAWXUl4jTqSCZ0MFjw?pwddw52 提取码:dw52 1.在工程中新建FreeRTOS文件夹 2.把源码source里面的文件全部粘贴进FreeRTOS文件夹中 3.在portable文件中只保留一下文件,…

【剑指offer刷题记录 java版】数组双指针 之 二分搜索

本系列文章记录labuladong的算法小抄中剑指offer题目 【剑指offer刷题记录 java版】数组双指针 之 二分搜索 剑指 Offer 53 - I. 在排序数组中查找数字 I剑指 Offer II 068. 查找插入位置剑指 Offer 04. 二维数组中的查找剑指 Offer II 069. 山峰数组的顶部剑指 Offer II 073. …

java周期性线程池newScheduledThreadPool介绍,多线程下载url文件(断点下载、进度展示、网速展示、剩余时间展示)

文章目录 一:newScheduledThreadPool(周期性线程池)1.1 特点1.2 核心线程数1.3 创建实例1.4 常用方法1.4.1 schedule方法1.4.2 scheduleAtFixedRate方法1.4.3 scheduleWithFixedDelay方法 二:多线程下载展示文件总大小、剩余时间、…

基于SpringBoot+vue的简历系统设计和实现

博主介绍: 大家好,我是一名在Java圈混迹十余年的程序员,精通Java编程语言,同时也熟练掌握微信小程序、Python和Android等技术,能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架下…

ESXi 7.0 U3m HPE (慧与) 定制版 OEM Custom Installer CD

VMware ESXi 7.0 Update 3m - 领先的裸机 Hypervisor (All OEM Customized Installer CDs) ESXi 7.0 U3m Standard (标准版) ESXi 7.0 U3m Dell (戴尔) 定制版 OEM Custom Installer CD ESXi 7.0 U3m HPE (慧与) 定制版 OEM Custom Installer CD ESXi 7.0 U3m Lenovo (联想) 定…

基于SSM的养老机构信息管理系统设计与实现

摘 要 随着我国老年人逐渐增加,老人们的子女数量减少,工作时间过长无暇照顾父母,导致养老院和护工需求量大幅上涨。伴随我国生活水平提高的同时对老年人的护工人员的要求也越来越高。根据以上要求关于养老院有很多的信息需要进行管理&#…

Zero-Shot, One-Shot, and Few-Shot Learning概念介绍

导语 本文将介绍零样本学习、一次样本学习和少样本学习的概念,它们使得机器学习模型能够在仅有有限数量的示例情况下对对象或模式进行分类和识别。 在机器学习中,我们通常需要大量的训练数据来训练模型,以便它能够准确地识别和分类新的输入…

数据挖掘知识与学习方向

数据挖掘是一门涉及多个学科的交叉学科,需要掌握的知识点也比较多。以下是一些数据挖掘的基础知识和学习方向: 数据库:需要掌握 SQL 语言和数据库设计,以便能够有效地管理和处理数据。 统计学:需要掌握基本的统计学知…

硬盘初始化后数据还能恢复吗?硬盘被初始化怎么恢复数据

现今热门的数据恢复话题之一便是硬盘被初始化后如何恢复数据。或许许多人都遭遇过这一问题,往往因为误操作或不小心,导致硬盘数据被不可逆地清除。所以,为帮助广大用户避免数据丢失的情况,本文将为大家介绍如何恢复被初始化的硬盘…

基于注解的Spring(IOC+AOP)

目录 这是基于黑马Spring的笔记 写再前面 开始 Component(valuebean的名称) componet衍生出的3个注解 Bean内部的属性进行注入 非自定义的Bean管理 使用配置类完全替代XML配置文件 配置类中的注解 spring中的其他注解(偶尔会用到) Spring注解的解析原理 sp…

Java Supervisor RPC2 接口对接

1.引入xmlrpc-client 如果是C#语言&#xff0c;请参考《C#对接supervisor XML-RPC API 实现进程控制》 如何安装Supervisor&#xff0c;请参考《Linux进程守护—Supervisor&#xff08;ubuntu&#xff09;》 如果是Maven项目&#xff0c;则在pom.xml引入jar包 <dependenc…

详解CSS中的flex布局

详解CSS中的flex布局 1、概念2、容器属性2.1 flex-direction2.2 flex-wrap2.3 flew-flow2.4 justify-content2.5 align-items2.6 align-content 3、元素属性3.1 order3.2 flex-grow3.3 flex-shrink3.4 flex-basis3.5 flex3.6 align-self 1、概念 弹性盒子&#xff08;display: …

如何系列 JMeter如何录制脚本

文章目录 方式1. 手动编写2. JMeter自带录制功能3. Fiddler录制4. Badboy录制5. Blazemeter录制 总结和使用感受 方式 1. 手动编写 最原始的方式&#xff0c;在线程组中根据研发提供的接口文档和浏览器的Network请求一个个手动录入&#xff0c;它可以提供更大的灵活性和控制力…