如何在网页端给视频添加硬水印

前言

做音视频的同学都知道播放器展示的视频水印分为软水印和硬水印两种,水印的作用一方面是用来增加视频画面的展示内容,比如广告效果、特效处理等;另一方面是出于版权保护的目的,防止视频盗录的风险。一般实现视频的软水印是在播放器端进行处理的,安全性会比较差。硬水印则是在视频源上进行处理,将水印打到视频里,成为视频内容的一部分,这样安全性就会非常高。

软水印

软水印,就是在播放器的视图 view 上做操作,比如增加 logo、贴图、马赛克等。其实,常见的视频网站很多时候都是使用播放器实现软水印。

从整体上的效果来讲,软水印和跑马灯、弹幕差的实现效果差不多。

硬水印

硬水印,就是通过视频转码,将 logo、贴图、马赛克等信息以数据元素的形式和原视频画面融合在一起,成为视频内容的一部分。在播放过程中,不需要我们控制他们的显示时机、位置、透明度等,因为这些控制因素在转码的时候就确定了。当播放带硬水印的视频时,直接使用播放器播放就行了,不需要关心水印的位置和透明度等信息,从一定程度上减少了播放器的处理逻辑。

CCTV 直播举例接下来举一个比较形象的例子,请看下图。

上图,我一共圈出了三个区域,其中,区域 1(左上角)是一个非常明显的硬水印,一般各个电视台在购买影视资源后,都会叠加自己台标,一方面是方便入库,另一方面是保护版权,防止资源盗用。区域 2(右上角)一般是一个软水印(少数情况也可能是硬水印),会根据当前播放视频的清晰度动态切换,如果片源切成 4K,右上角的图标中的“高清”很可能就是“4K”了。区域 3(偏右下角)是“超级硬水印”,哈哈,它作为视频画面的原始内容,其实和区域 1 的硬水印是完全一样。

通过上面的介绍,我相信大家应该对原视频画面、软水印、硬水印就能够理解的非常清楚了。

软水印的缺点

软水印在移动端 APP 和 PC 客户端播放时安全性还比较高,毕竟客户端都是我们自己开发的,播放逻辑控制起来也相对受控,但是 Web 端却面临着较大的安全因素,因为浏览器是完全不受控的,如果原始视频流没有添加硬水印信息,被别有用心的人通过浏览器的开发者模式拿到视频源的 URL,就可以任意传播了,不论是直播场景还是点播场景,难免会造成一些损失。

面对这样的问题,WebRTC 在网页端应该怎样处理呢?

“抛砖引玉”

今天们就来介绍一下如何实现网页端添加 WebRTC 硬水印。

开始前,我们先了解一下,正常情况下,WebRTC 是如何在网页端渲染视频画面的。没错,熟悉 WebRTC 使用背景的同学都会知道,网页渲染视频画面使用的是 video 标签。我们本事不需要过多的关心 video 标签内部的实现原理,只需要掌握它的使用方法就够了。

比如,创建本地视频流后,如何渲染呢,下面通过一个小例子来介绍。

const getUserMedia = (mediaConfig, cb, errorCb) => {
    let effects = [];
    let userMediaConfig = mediaConfig.watermark;
    if (userMediaConfig.watermarkFont || userMediaConfig.watermarkImg) {
        wm = new WaterMarkHelpers.WaterMark();
        imgCopy = new WaterMarkHelpers.ImageCopy();
        effects.push(imgCopy);
        // 文字水印信息
        if (typeof userMediaConfig.watermarkFont == 'boolean' && userMediaConfig.watermarkFont == true) {
            fontEffect = new WaterMarkHelpers.FontEffect(userMediaConfig.font,
                userMediaConfig.words,
                userMediaConfig.fontColor,
                userMediaConfig.fontX,
                userMediaConfig.fontY,
                userMediaConfig.fontSize);
            effects.push(fontEffect);
        }
        // 图片水印信息
        if (typeof userMediaConfig.watermarkImg == 'boolean' && userMediaConfig.watermarkImg == true) {
            imgEffect = new WaterMarkHelpers.ImageEffect(userMediaConfig.image,
                userMediaConfig.imgX,
                userMediaConfig.imgY,
                userMediaConfig.imgWidth,
                userMediaConfig.imgHeight,
                userMediaConfig.imgAlpha);
            effects.push(imgEffect);
        }

        wm.setManipulators(effects);
        wm.sbStartCapture(userMediaConfig)
            .then(cb)
            .catch(errorCb);
    } else {
        mediaDevices
        .getUserMedia(userMediaConfig)
        .then(cb)
        .catch(errorCb);
    }
};

上面的例子实现了通过浏览器的 getUserMedia 接口获取本地的摄像头和麦克风的媒体流,然后通过 video 标签在本地进行渲染的功能。

了解了 WebRTC 基础的视频渲染逻辑后,我们再来看硬水印的添加方案。

硬水印的具体实现

首先我们创建一个非常小的 video 标签,大小可以为 88,甚至可以是 11,但是最好不要为 0,不然某些浏览器可能会在支持 video 标签的某些特性时表现不好。

然后获取本机的摄像头视频流,然后渲染到一个 canvas 上,同时,我们将提前准备好的文字水印和图片水印也渲染到该 canvas 上,自动形成原始图像和水印信息的合并。

注意:我们需要控制好帧率,比如 66 毫秒在 canvas 上渲染一次,这样采集的视频大概是 15fps。

最后,我们再从这个 canvas 上获取我们需要的目标视频流,实现我们的想要的硬水印效果。

接下来,看一下部分实现代码:

let effects = [];
let userMediaConfig = mediaConfig.watermark;
if (userMediaConfig.watermarkFont || userMediaConfig.watermarkImg) {
    wm = new WaterMarkHelpers.WaterMark();
    imgCopy = new WaterMarkHelpers.ImageCopy();
    effects.push(imgCopy);
    // 文字水印信息
    if (typeof userMediaConfig.watermarkFont == 'boolean' && userMediaConfig.watermarkFont == true) {
        fontEffect = new WaterMarkHelpers.FontEffect(userMediaConfig.font,
            userMediaConfig.words,
            userMediaConfig.fontColor,
            userMediaConfig.fontX,
            userMediaConfig.fontY,
            userMediaConfig.fontSize);
        effects.push(fontEffect);
    }
    // 图片水印信息
    if (typeof userMediaConfig.watermarkImg == 'boolean' && userMediaConfig.watermarkImg == true) {
        imgEffect = new WaterMarkHelpers.ImageEffect(userMediaConfig.image,
            userMediaConfig.imgX,
            userMediaConfig.imgY,
            userMediaConfig.imgWidth,
            userMediaConfig.imgHeight,
            userMediaConfig.imgAlpha);
        effects.push(imgEffect);
    }

    wm.setManipulators(effects);
    wm.sbStartCapture(userMediaConfig)
        .then(cb)
        .catch(errorCb);
} else {
    mediaDevices
    .getUserMedia(userMediaConfig)
    .then(cb)
    .catch(errorCb);
}

};

我们改造了系统的 getUserMedia 方法,在其中引入了硬水印的添加逻辑,我们区分图片水印和文字水印的实现方式。

同时,我定义了水印接口的使用方式:

@param {Boolean} opts.watermarkImg - 是否启用图片水印

@param {String} opts.image - 图片(支持图片链接和在服务器配置相对路径)

@param {Number} opts.imgX - 图片水印坐标 x

@param {Number} opts.imgY - 图片水印坐标 y

@param {Number} opts.imgWidth - 图片水印宽度

@param {Number} opts.imgHeight - 图片水印高度

@param {Number} opts.imgAlpha - 图片水印透明度

@param {Boolean} opts.watermarkFont - 是否启用文字水印

@param {String} opts.font - 字体

@param {String} opts.words - 文字水印内容

@param {String} opts.fontColor - 文字水印颜色

@param {Number} opts.fontSize - 文字水印字号

@param {Number} opts.fontX - 文字水印坐标 x

@param {Number} opts.fontY - 文字水印坐标 y

结尾

这样,我们就巧妙的在 Web 端实现了视频流的硬水印方案。希望这个思路可以帮到大家,自己实现这个功能的过程中也踩了不少坑,欢迎感兴趣的小伙伴沟通交流。

推荐阅读
相关专栏
音视频杂谈
161 文章
本专栏仅用于分享音视频相关的技术文章,与其他开发者和声网 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。