写点什么

基于 Agora Web SDK 自定义直播画面

用户头像
dajyaretakuya
关注
发布于: 2021 年 05 月 17 日
基于Agora Web SDK自定义直播画面

1 什么是自定义直播画面

默认情况下,我们的直播是以摄像头的直出画面为数据源进行推流的,但很多时候默认的直出画面无法满足业务需要,例如在直播的时候我们可能想要在画面的右上角加上自己的 Logo。这种时候就需要使用一些方法先捕获视频源进行预处理以后再进行发送。不同平台处理视频源的方法不同,接下来我们以使用 Agora Web SDK 4.x 中基于 Web RTC 的 API 来描述如何获取视频源并进行数字合成。


使用 Agora Web SDK 做直播的基本流程很简单,其中最重要的两个部分是“推流鉴权”和“设置音视频源”。默认情况下所有的操作都已经经过了 Agora Web SDK 的高度封装,但如果我们需要对已有的视频源进行二次合成,就需要修改默认的配置。Agora Web SDK 提供了自定义视频采集的API,API 提供了一个方法createCustomVideoTrack用来创建自己的视频轨道(Video Track),在使用这个 API 之前,我们先简单回顾一下浏览器的 Media Streams API。

2 媒体捕获和流控制接口 (Media Streams API)

2.1 基本概念

现代浏览器支持一整套完善的 API 去应对各种功能需求,大大增强了浏览器的处理能力,我们现在要讨论的媒体捕获和流控制接口 (Media Capture and Streams API),简称为媒体流接口 (Media Streams API),是专门为 WebRTC 设计的一套流媒体数据 API,他提供的类和方法可以用于处理数据流以及构成这些数据流的轨道 (track)。


Media Streams API 主要和Media Steam打交道,Media Stream是一个用来表示音视频相关数据的对象,一个Media Stream由多个Media Stream Track对象构成 (也可以一个也不包含)。每个Media Stream Track对象可以包含多个channelchannelMedia Stream的最小数据单位。任何流数据结构都有输入和输出,Media Stream也不例外,他可以接受一个摄像头作为视频输入,并且输出到一个<video>标签里。

2.2 Media Stream Track

上面我们提到一个 Media Stream 可以由多个 Media Stream Track 组成。为了获取 Media Stream,通常我们可以调用MediaDevices.getUserMedia()接口。


navigator.mediaDevices.getUserMedia({ video: true, audio: true }).then(function(stream) {    var video = document.querySelector('video');     //把我们的视频流插入video标签      video.src = window.URL.createObjectURL(stream); }).catch(function(err){    //错误处理    console.log(err);});
复制代码


获取 Stream 后,把我们的视频流插入 HTML 里的video标签,一个最简单的例子就完成了。其中,{ video: true, audio: true }是一个MediaStreamConstraints对象,用于说明请求的媒体类型。如果浏览器无法找到制定的媒体类型,那么就会进入到错误处理代码块。更多详细的使用方法可以参考官方文档

2.3 如何修改 Stream

目前,如果我们需要直接对这个 Stream 进行二次修改,最常用的方法是借助于 Canvas,把视频流内容输出到canvas元素中,然后对其进行后处理,最后把 Canvas 里的内容再次作为 Media Stream Track 添加到 Media Stream 中,就能达到二次处理的目的了。在看具体的代码之前,我们先回顾一下数字视频合成的一些基本概念,对这部分不感兴趣的读者可以直接跳到第 4 节。

3 数字视频合成

3.1 数字视频制作管线

现代数字视频制作管线通常分为三个阶段:前处理 (Pre-Production) ,生产 (Production) 和后处理 (Post-Production) [1]。前处理主要包含视频创作的设计、团队构建以及其他准备工作,生产阶段就是拍摄,后处理则包含剪辑、音效、音乐、颜色调整以及视觉效果。可见,后处理的比重占了很大一块,我们现在看到的很多软件,诸如NukeDaVinci ResolveAfter Effects都是用于做后处理的。数字视频制作管线可以简单的总结为下面的流程图 [2]。



其中,实心红色箭头代表图像,虚线红色箭头代表摄像机信息,黑色箭头代表 3D 数据。

3.2 数字合成

从上一节的流程图可以看出,数字合成也是后处理的一部分,并且所有数据最终都会流向数字合成中,因此数字合成理论在整个视频制作管线中非常重要。最早的数字合成理论要追溯到 1981 年 Wallace 的研究 [3]。现代主流数字合成基于两种方式:节点 (Node) 和图层 (Layer) [4],无论哪种方式,其底层图像处理的概念是一致的,在数字合成理论中,最重要的两个概念是透明度 (Alpha) 和预乘 (Premultiplication)。透明度就是我们熟知的RGBA中的A值。无论是 3D 建模软件还会 2D 图像处理软件都要根据这个数值进行最终的图像数据合成。预乘则是一个可选的操作,顾名思义就是预先把颜色值和透明度值相乘。例如,RGB (1.0, 0.5, 0.7)在透明度为 0.5 的情况下,预乘后的值是RGB(0.5, 0.25, 0.35)。从计算的角度,预乘可以提升合成阶段的性能,因为透明度值已经被预处理过了,但预乘也有缺陷,会影响到诸如颜滤镜、渐变以及绿背景移除等操作的准确度。


在视频剪辑软件中,通常可以选用预乘来优化视频处理性能,而在直播视频画面二次处理中,我们的数字合成通常是实时计算的,在下面的章节中,我们将会介绍如何利用 Agora Web SDK 进行自定义的视频处理。

4 自定义 Media Stream Track

4.1 基于 Canvas 的方案

我们基于 Agora官方示例basicLive项目进行改造。自定义视频流遵循下面的流程。



首先我们在index.html页面中引入自己的videocanvas


<div class="col">    <video controls="controls" src="" id="my-demo" style="display:none;"></video>    <canvas id="mycanvas" width="800" height="600"></canvas></div>
复制代码


然后在basicLive.js中下列代码


var imagesLoaded = 0;var imgTitle = loadImage("./title.png", imageLoadCompleted);var context = mycanvas.getContext("2d");var video = document.getElementById("my-demo");
function loadImage(src, onload) { var img = new Image(); img.onload = function() { onload(); }; img.src = src; return img;}
function imageLoadCompleted() { imagesLoaded += 1; // 这里只有一张图需要加载,因此已加载图片数量大于等于1即可 if(imagesLoaded>=1) { if (navigator.mediaDevices === undefined) { navigator.mediaDevices = {}; } var constraints = { audio: true, video: { width: 1280, height: 720 } };
navigator.mediaDevices.getUserMedia(constraints) .then(function(mediaStream) { var video = document.querySelector('video'); video.srcObject = mediaStream; video.onloadedmetadata = function(e) { video.addEventListener('loadeddata', function() { updateVideoToCanvas(); }); video.play(); }; }) .catch(function(err) { console.log(err.name + ": " + err.message); }); }} function updateVideoToCanvas() { context.clearRect(0, 0, mycanvas.width, mycanvas.height); context.drawImage(video, 0, 0, 800, 600); context.globalAlpha = 1; context.drawImage(imgTitle, 0, 0); requestAnimationFrame(updateVideoToCanvas);}
复制代码


上述代码首先获取本地设备的视频输出,并写入video中,注意此处绑定了videoloadeddata事件,该事件处理函数中调用updateVideoToCanvas用来做最终的数字合成,该方法提取 video 的当前帧内容,并与图片进行合并,然后输出合并后的内容写入canvas,最后调用requestAnimationFrame渲染下一帧画面。


下一步,我们删除原来使用 AgoraRTC.createCameraVideoTrack()创建 media stream 的方法,替换为自定义 media stream


canvasStream = mycanvas.captureStream(25);const [videoTrack] = canvasStream.getVideoTracks();let localVideoTrack = AgoraRTC.createCustomVideoTrack({    mediaStreamTrack: videoTrack,});localTracks.videoTrack = localVideoTrack;
复制代码


这样就成功取代了默认摄像头的画面。完整的代码可以参考我的仓库下的basicLive目录。

4.2 基于 Insertable Stream 的方案

实际上,上述借助于 Canvas 的方案略显繁琐,因为总会要使用一个canvas作为中转站。因此现在有一种新的 API 被提提出,这就是Insertable streams,也就是可插入流,这个 API 的核心思想在于我们需要把MediaStreamTrack的内容暴露成一个流的集合 (collection of streams),这些流可以被开发者直接操作,这样就避开了中间canvas多余的流程。


这套 API 目前还处于起步阶段,如果想了解更多内容,可以参考这个页面

4.3 动画合成

上面的例子我们只合成了一张静态带透明度的图片,实际上我们还可以添加更丰富的合成,例如合成一个 SVG 动画。合成动画需要考虑更多细节,例如动画的时间轴如何与视频流匹配。此外,我们还可以使用 WebGL 开发动态效果叠加在视频上,这样可以实现更多自定义的直播操作。不过这类内容涉及到更多图形领域的知识,一篇文章无法完全解明,感兴趣的读者可以阅读一下 David Geary 的《HTML5 Canvas 核心技术》以及 Kouichi Matsuda 和 Rodger Lea 编写的《WebGL 编程指南》。

5 参考文献

[1] The VES Handbook of Visual Effects, 3rd Edition (ISBN 9781138542204)

[2] Sebastian Sylwan, "The Application of Vision Algorithms to Visual Effects Production". ACCV 2010, Part I, LNCS 6492, pp. 189–199, 2011.

[3] Wallace, Bruce A., Merging and Transformation of Raster Images for Cartoon Animation, Computer Graphics, Vol 15, No 3, Aug 1981, 253-262. SIGGRAPH’81 Conference Proceedings, doi:10.1145/800224.806813.

[4] Lee Lanier, Professional Digital Compositing: Essential Tools and Techniques, 2009 (ISBN 0470452617)

发布于: 2021 年 05 月 17 日阅读数: 46
用户头像

dajyaretakuya

关注

我们仍未探究到世界之理。 2017.11.11 加入

喜欢看理科书,爱玩游戏、看动画、写代码、研究理论知识。

评论

发布
暂无评论
基于Agora Web SDK自定义直播画面