移动直播技术知多少:基础原理解析 & 腾讯云直播接入

本文可以了解到

移动端视频直播相关的基础知识,以及如何利用腾讯云直播 SDK 搭建自己的直播系统。

前言

1. 视频时代已经来临

当今的互联网,视频已经成为一股洪流,冲刷着每一个人。

2020 年,由于新型冠状病毒疫情的爆发,视频直播互动更是一飞冲天,在网购、游戏、教育、金融等等方面都呈现爆发式发展。

可见音视频相关的技术,已经是我们不得不去了解的内容了。

2. 视频开发包括哪些内容

移动端的音视频开发一般有:短视频和视频直播互动。

在本人的【Android 音视频开发打怪升级】系列文章中,主要讲解的就是「短视频开发」相关的知识。

而「直播」涉及到的技术,更加庞大,可以说是「短视频开发」的一个超集,因为它不仅涉及到本地视频的编辑,还涉及到直播服务器的架设,以及面对不稳定网络的优化等等,搭建一个直播系统并非易事。

那么你肯定要问了,既然很难,那我直接用第三方的 SDK 就可以了,干嘛还要学习这些晦涩难懂的视频知识?

3. 为什么学习直播基础知识

如果不了解基本知识,直接接入第三方的 SDK 可以吗?当然可以!

但俗话说得好:工欲善其事,必先利其器

如果不了解其中原理,在未来遇到问题时,要么难以有效地解决,要么需要付出比现在成倍的努力。

一、直播基础知识

最原始的直播系统其实并没有想象的那么复杂,无非就是主播端将音视频数据推送到服务器,观众端则从服务器拉取数据播放。

1. 基础流程

通过下面这个数据流程图,能清晰地看到整个直播的过程。

最简单的直播系统

可以看到,「主播客户端」处理的事情,其实就是短视频开发中最重要的内容:

流程

详细操作

音视频数据采集

通过摄像头和麦克风采集

音视频滤镜

通过 OpenGL 和 SoundTouch 等工具实现音视频编辑

音视频编码

通过系统硬编码 或 FFmpeg 软编码,将数据编码为 H264 和 AAC

数据封装打包

将编码好的数据封装成指定的格式

唯一不一样的地方,短视频会将封装好的数据保存到本地,直播则是通过 推流协议 将数据推送到服务器。

关于 H264AAC封装格式 等知识,不清楚的可以查看这篇文章【音视频基础知识】。

  • 推流

推流,是直播中的一个术语,意思是将流媒体数据推送到服务器。如何推流,关键就在于使用的推流协议。

  • 拉流

拉流,指的是「观众端」流媒体数据的拉取,同样也需要通过约定的拉流协议来拉取。

2. 直播协议

直播协议包含了上面提到的 「推流」和「拉流」协议。

主要有一下三种:

  • RTMP

RTMP 全名:Real Time Messaging Protocol,实时消息传送协议。是 Adobe 公司开发的,用于 Flash Player 和服务器间之间传输音视频数据。RTMP 是基于 TCP 开发的,属于应用层的协议,默认端口为 1935

RTMP 主要特点是实时性好,延时比较低(1~3s)。既可以用来推流,也可以用来拉流。

但是由于其基于 TCP 长链接协议,默认端口非公共端口,可能会被防火墙拦截。

RTMP 视频数据封装格式为 flv

  • HTTP-FLV

HTTP-FLV,从其命名可以大概看出工作模式。即:将音视频数据封装为 flv 格式,通过 HTTP 长链接协议传输。既然是基于 HTTP ,其默认端口就是 80,可以直接穿透防火墙。

其传输方式和 RTMP 一样,只是将协议更换为 HTTP,所以实时性也比较好。

由于 HTTP-FLV 的特点,非常适合用于 App 直播拉流。

  • HLS

HLS:HTTP Live Streaming。是苹果公司推出的基于 HTTP 的流媒体传输协议,视频封装格式是 ts,在 iOS 和 Mac 支持比较好。

RTMPHTTP-FLV 不同的是,HSL 是切片传输,它会将视频切为一个个小的 ts 文件,并将切片信息记录在 .m3u8 文件中。

拉流客户端根据 .m3u8 中的 ts 索引信息,按顺序下载播放。

由于其切片的特点,会导致比较大的延迟,在实时性要求比较高的情况下,效果不好。

以上,就是在直播系统中经常使用到的三大协议。

综合以上特点,推流的时候,经常使用的是 RTMP 协议;拉流播放的时候,经常使用的是 HTTP-FLVHLS 协议。

3. 直播服务器

有了协议,那么客户端和服务器就可以通讯了,怎么样搭建服务器呢?

服务器:一般都是 Nginx + 协议拓展模块。

协议拓展模块

i. nginx-rtmp-module ,不支持 http-flv;

ii. nginx-http-flv-module,兼容nginx-rtmp-module,并支持 http-flv 直播;

iii. srs(simple-rtmp-server),支持三种协议。

搭建服务器也是一个比较繁琐的过程,涉及到的知识也很多,本文不再深入,网上也有很多相关资料可查看。

二、直播中的重难点

在直播中,有几个非常重要的地方,会直接影响直播效果,导致用户流失。

1. 首屏时间

首屏时间,即从观众打开直播,到看到画面呈现出来的时间。影响这个时间的是 H264 编码中的一个概念: GOP

  • GOP

全称:Group of Picture。即一组帧组成的一个序列。

H264 中,分别有 I帧、P帧、B帧 三种帧类型。

GOP 就是由一个 I帧 和多个 P帧B帧 组成的一组相近的画面 。

GOP

解码器可以直接解码 I帧 ,但是 P帧B帧 必须依赖 I帧,或者前后的 P 或 B 才能解码。

首次连上直播间时,需要抛弃掉 PB 帧,等待 I帧

所以,影响首屏时间最重要的因素就是 I帧,也就是两个 GOP 之间的间隔时间。

GOP 间隔的设置并非越小越好,太小则两个 I帧 之间的 P/B帧 越少,压缩率越低,画面质量越差,需要做好权衡。

2. 稳定性问题

我们知道网络是不稳定的,经常会出现网速慢,甚至断网的问题,所以稳定性优化也是非常重要的。

比如以下几个方面:

  • 码率控制

同样分辨率下,码率越高,视频越清晰,同时需要的带宽也越大。相反,码率越低,视频越模糊,数据越小。

  • 弱网优化

根据不同的网速切换不同的码率进行播放等。

  • 断线重连

网络断开时的重联机制。

3. 全局负载均衡

随着业务的发展,如果主播和观众的数量越来越多以后,系统可能会面临高并发情景,按照上面介绍的最简单的系统,可能就扛不住了。

直播卡顿,甚至系统奔溃,将直接影响公司的声誉。解决这种情况的一个好办法就是使用 CDN

  • CDN 内容分发

解决因分布、带宽、服务器性能带来的访问延迟问题,适用于站点加速、点播、直播。

关于 CDN 可参考: CDN是什么?使用CDN有什么优势?

加入 CDN 后,整个直播系统架构如下:

CDN

三、其他

除了以上提到的内容,当今的直播系统还要包括以下内容:录制转码鉴黄截屏权鉴防盗回声消除连麦 等等,整套下来,需要非常多的知识储备,以及大量的时间精力,才能完成。

大部分公司其实很难完成,而国内大厂们则利用自己强大的研发能力,提供了各自的 SDK ,大大降低了直播门槛。

实际上,一些知名的直播公司,也是利用了第三方的 SDK 来搭建自己的直播系统的,比如斗鱼、龙珠直播、now直播等等,都是利用了腾讯云直播平台搭建的。

下面,就来看看如何使用腾讯云直播 SDK 来搭建直播系统。

四、使用腾讯云直播 SDK ,搭建直播系统

先通过腾讯云直播的架构图,了解一下腾讯云直播提供了哪些功能。

图片上传失败...(image-61e3ca-1587690635760)

可以看到,腾讯云直播中提供了最基础的 推流拉流录制转码点播,以及 推流加速CDN加速。除了这些,还有 控制台,以及更加灵活的 API 控制接口等等。

基本上,一个直播系统该有的功能基本都具有了。

1. 开通云直播服务

要使用腾讯云直播功能,需要先注册腾讯云账号,完成注册后,进入控制台,然后搜索「云直播」,打开对应的页面,点击【申请开通】即可开通云直播服务。

开通后,会赠送 20GB 国内播放流量,可以用来测试。

  • 申请移动端 License

想要在 Android/iOS 手机上使用云直播,需要先申请对应的 License 。当然了,这是需要付费才能使用的。

不过腾讯提供了一个免费的测试版 License ,可以免费使用 28 天。这里使用测试版的 License 进行演示。具体如下:

进入「云直播」控制台后,点击左侧的「直播 SDK」-「License」。

点击「立即申请」,填写 App 名字Android 包名iOS Bundle ID

申请License

填好以后,点击「确定」,得到 KeyLiscenseUrl,这两个值在后面接入 SDK 的时候将会用到。

获取Key和LiscenseUrl

  • 添加自有域名

虽然腾讯云直播已经搭建好了直播服务器,但是 工信部 规定,直播需要备案域名,所以如果没有服务器和域名的话,需要先购买服务器,注册域名,然后对域名申请备案。

需要注意的是:云直播需要两个不同的域名,一个用作推流,一个用于播放。

腾讯云已经为我们提供了一个 推流域名**,所以我们其实只需要提供一个域名用于播放就可以了。**

添加流程如下:

「云直播控制台」- 「域名管理」-「添加域名」

添加域名

添加完成后,会生成域名对应的 CNAME(CNAME 很关键,下面详细讲解)。

生成CNAME

到这里域名就配置完毕了。

  • 配置CNAME

如果你一个 “纯粹” 的移动端开发者,不怎么了解服务端的话,你肯定会觉得很奇幻:刚刚配置的播放域名有什么用呢?它是怎么和腾讯云的服务器进行关联的呢?

关键的地方就在这个 CNAME 上。

我们知道,手机/PC 在访问一个域名的时候,会向 DNS 发起请求,普通情况下,DNS 会将域名解析成 IP 地址,然后返回给 手机/PC ,接着通过这个 IP 发起真正的请求。

但是,实际上并非总是这样的,我们可以给一个 域名A 配置一个 别名 ,这个别名也是一个 域名B


当客户端对 A域名 发起请求时,DNS 会找到 A ,说:“给我 IP 吧”,这时候 A 会告诉 DNS : “我没有,你去找 B域名 要吧”。

DNS 转向 B ,并且获取到了 B 的 IP 地址,然后返回给客户端,客户端对 B 的 IP 发起了请求。


这时候的 A域名 就变成了类似一个中转站的东西,对 A域名 的请求,实际上访问的是 B域名

CNAME 就是 别名 的意思。也就是说,上面腾讯云生成的 CNAME 域名,是不能直接访问的,还需要把它和我们自己备案的 播放域名 进行关联。

当我们访问自己的 播放域名 的时候,实际上是访问了腾讯云的直播服务器。

弄清楚了这层关系以后,配置就很简单了。

至于怎么关联,各大云服务提供商都会提供 CNAME 的配置,按照云服务提供商的说明进行配置就可以了。

这也说明了,腾讯云直播可以绑定任意的云服务,你可以使用腾讯云的,也可以使用阿里云的。只要备案好,并配置 CNAME 即可。

2. 集成 SDK

完成了以上配置以后,一个基础版的直播服务就开通了。接下来,只需在客户端集成 SDK 就可以实现推流和拉流播放了。

Android 端为例,说明整个集成过程。

  • 依赖配置

腾讯云提供了多种集成方式,jar/aar/gradle 依赖,推荐使用 gradle 依赖,方便简单,在 app/build.gradle 添加:

代码语言:javascript
复制
dependencies {
   implementation 'com.tencent.liteavsdk:LiteAVSDK_Smart:latest.release'
}

配置 so 架构,腾讯云直播支持 armeabi 、 armeabi-v7a 和 arm64-v8a,你可以选择这几个架构都适配,也可以选择其中的一个架构,在 app/build.gradle 添加:

代码语言:javascript
复制
defaultConfig {
   ndk {
       abiFilters "armeabi", "armeabi-v7a", "arm64-v8a"
   }
}
  • 配置打包参数

在 app/build.gradle 中配置

代码语言:javascript
复制
android {
//省略其他...

packagingOptions {
    pickFirst '**/libc++_shared.so'
    doNotStrip "*/armeabi/libYTCommon.so"
    doNotStrip "*/armeabi-v7a/libYTCommon.so"
    doNotStrip "*/x86/libYTCommon.so"
    doNotStrip "*/arm64-v8a/libYTCommon.so"
} 

//......

}

  • 配置权限

在 AndroidManifest.xml 中配置

代码语言:javascript
复制
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-feature android:name="android.hardware.Camera"/>
<uses-feature android:name="android.hardware.camera.autofocus" />
  • 初始化SDK

在 Application 类中配置 License 的 Key 和 LicenseUrl

代码语言:javascript
复制
class App : Application() {
override fun onCreate() {
super.onCreate()

    // 替换成控制台获取到的 licence url
    val licenceURL = &#34;http://license.vod2.myqcloud.com/license/v1/xxxxx&#34; 
    // 替换成控制台获取到的 licence key
    val licenceKey = &#34;e90e0872aaeb7797xxxxxxxxx&#34; 
    
    TXLiveBase.getInstance().setLicence(this, licenceURL, licenceKey)
}

}

3. 推流与拉流

实现推流和拉流非常简单,腾讯云已经把功能都封装好了。

推流:摄像头/麦克风数据采集、编码、视频渲染、推送数据。
拉流:拉取数据、解码、渲染播放。

我们要做的就是简单地调用一下 API 。

3.1 实现数据推流

直播 SDK 提供了一个 View 用于视频的播放显示:TXCloudVideoView。把它放到 xml

中即可,SDK 会根据 TXCloudVideoView 的宽高和视频的宽高,做对应的自适应缩放。

代码语言:javascript
复制
// activity_push.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

&lt;com.tencent.rtmp.ui.TXCloudVideoView
    android:id=&#34;@+id/tx_cloud_video_view&#34;
    android:layout_width=&#34;match_parent&#34;
    android:layout_height=&#34;match_parent&#34; /&gt;

&lt;Button
    android:layout_width=&#34;wrap_content&#34;
    android:layout_height=&#34;wrap_content&#34;
    android:text=&#34;推流&#34;
    android:onClick=&#34;startPushing&#34;/&gt;

</androidx.constraintlayout.widget.ConstraintLayout>

接着,在 Activity 中初始化推流工具,并把 TXCloudVideoView 绑定到 SDK ,实现摄像头预览。

代码语言:javascript
复制
class VideoPushActivity : AppCompatActivity() {
private lateinit var mLivePusher: TXLivePusher

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_push)
    initPusher()
}

private fun initPusher() {
    val mLivePushConfig = TXLivePushConfig()
    
    mLivePusher = TXLivePusher(this)
    
    // 一般情况下不需要修改 config 的默认配置
    mLivePusher.config = mLivePushConfig

    mLivePusher.startCameraPreview(tx_cloud_video_view)
}

fun startPushing(v: View) {
    val rtmpURL = &#34;rtmp://2157.livepush.myqcloud.com/live/demo?txSecret=39193bda8a439ba8852baf9bce1fde39&amp;txTime=5E8DF4FF&#34;
    val ret = mLivePusher.startPusher(rtmpURL.trim())
    if (ret == -5) {
        Log.i(&#34;VideoPushActivity&#34;, &#34;startRTMPPush: license 校验失败&#34;)
    }
}

override fun onDestroy() {
    super.onDestroy()
    mLivePusher.stopPusher()
    mLivePusher.stopCameraPreview(true)
}

}

是不是非常简单,初始化完毕后,只要把推流地址给到推流工具,启动推流就搞定了。

当然了,你还可以做更多的配置,比如

  • 通过 mLivePusher.setPushListener(this) 来监听推送状态和网络状态,详见【官方文档】;
  • 通过 TXLivePushConfig 来配置旋转角度、设置水印、帧率、码率等等,非常丰富的参数配置,详见【官方文档】;
  • 通过 mLivePusher.getBeautyManager() 获取到美颜工具 TXBeautyManager 可用于配置美颜相关的内容,不过这个方法是需要另外付费的。

最后,要注意的是,在退出推流页面的时候,需要在 onDestroy 中停止推流,并关闭、释放摄像头。

3.2 实现拉流播放

接下来,看看如何播放上面的推流视频。

视频的播放显示,依然是通过 TXCloudVideoView

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

&lt;com.tencent.rtmp.ui.TXCloudVideoView
    android:id=&#34;@+id/video_view&#34;
    android:layout_width=&#34;match_parent&#34;
    android:layout_height=&#34;match_parent&#34;/&gt;

&lt;Button
    android:layout_width=&#34;wrap_content&#34;
    android:layout_height=&#34;wrap_content&#34;
    android:text=&#34;播放&#34;
    android:onClick=&#34;startPlaying&#34;/&gt;

</androidx.constraintlayout.widget.ConstraintLayout>

同样的,在 Activity 中把 TXCloudVideoView 绑定给拉流工具。

代码语言:javascript
复制
// VideoPlayerActivity

class VideoPlayerActivity : AppCompatActivity() {
private lateinit var mLivePlayer: TXLivePlayer

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    initPlayer()
}

private fun initPlayer() {
    //创建 player 对象
    mLivePlayer = TXLivePlayer(this)

    //关联 player 对象与界面 view
    mLivePlayer.setPlayerView(video_view)
}

fun startPlaying(v: View) {
    val flvUrl = &#34;http://2157.liveplay.myqcloud.com/live/demo.flv&#34;
    mLivePlayer.startPlay(flvUrl, TXLivePlayer.PLAY_TYPE_LIVE_FLV)
}

}

拉流播放的过程也是非常简单,配置好拉流地址,启动播放就可以了。

需要注意的是,在 App 端,目前只支持 RMPTFLV 两种播放格式,在启动播放的时候,需要指定对应的格式。

代码语言:javascript
复制
mLivePlayer.startPlay(flvUrl, TXLivePlayer.PLAY_TYPE_LIVE_FLV)

mLivePlayer.startPlay(flvUrl, TXLivePlayer.PLAY_TYPE_LIVE_RTMP)

当然了,你也可以通过一些配置来达到自己的播放需求。比如:

  • mLivePlayer.setRenderRotation(TXLiveConstants.RENDER_ROTATION_LANDSCAPE) 设置横屏播放;
  • mLivePlayer.setRenderMode(int mode) 设置画面拉升模式:平铺模式/自适应模式;
  • mLivePlayer.setPlayListener(this) 监听播放状态和网络状态。

详见【官方文档】

3.3 生成推拉流地址
  • 地址格式

从上面推拉流的地址可以看出地址的组合格式,具体如下图:

推拉流地址格式

️注

只要符合上图规则的 URL 地址,腾讯云直播就认为是正确的,就可以正常推流和拉流。

前面四个参数很简单,根据自己的需求配置就可以了。

关键在于最后面的 权鉴 参数。

  • 权鉴

权鉴的用途是:防盗推/播!

腾讯云直播默认情况下,推流已经设置了权鉴,拉流是没有设置权鉴的。

⚠️

权鉴不是必填参数,也就是说,如果不启动权鉴,这个参数是可以去掉的,一样可以推拉流。 但是,没有权鉴的情况下,人家只要知道了服务器域名,就可以按照地址格式,拼接出合规的地址,直接就可以推拉流了。 言外之意就是,你的服务器被人盗了

而直播 SDK 的计费方式恰恰就是根据播放流量来计费的,如果被人家随便拉流播放的话,必然会造成损失。

代码语言:javascript
复制
// 无权鉴推流地址
rtmp://2157.livepush.myqcloud.com/live/demo

// 有权鉴推流地址
rtmp://2157.livepush.myqcloud.com/live/demo?txSecret=39193bda8a439ba8852baf9bce1fde39&txTime=5E8DF4FF

  • 开启权鉴

开启权鉴的方式也很简单,可以通过控制台或调用 API 设置。

⚠️

一般控制台设置只在测试的时候使用,生产环境下建议是通过自己的服务端调用 API 来配置权鉴 key ,并保持动态更新。

这里只是测试,我们就用控制台来配置。

「云直播控制台」-「域名管理」-「在要开启权鉴播放域名上,点击管理」-「访问控制」-「编辑」-「开启权鉴」-「设置主key、备key、过期时间」

开启播放权鉴

其中:

主key 很重要,用来参与推拉流地址中权鉴的计算。

备用key 主要用于当 主key 泄漏以后,可用 备用key 来生成地址,并且更新 主key

⚠️

权鉴的 key 千万不能泄露,只有自己和腾讯云知道,这样双方才能对权鉴中 MD5 的加密值进行验证。泄漏以后要赶紧更新。

  • 权鉴计算

从地址格式拼接图已经知道,权鉴的格式是:

代码语言:javascript
复制
txSecrect=Md5(key+StreamName+txTime)&txTime=xxxx

txTime:到期时间的 UNIX 时间戳。

比如过期时间为 2020-04-28 23:59:59,时间戳为 1586361599 。时间戳可以进一步转换为 16 进制,比如 1586361599 的 16 进制为 5E8DF4FF。

因此,

代码语言:javascript
复制
txTime = 1586361599

txTime = 5E8DF4FF

⚠️

拉流地址的 txTime 的计算有所不同,它的值等于【设置的时间 + 权鉴key 设置的过期时间】。

例如设置的过期时间为 2020-04-28 00:00:00权鉴key 的过期时间是 10s ,那么拉流地址真实的过期时间为:2020-04-28 00:00:10(UNIX时间戳十六进制为 5EA7018A)。

假设 权鉴key 为 123456 ,StreamName 为 demo,那么

代码语言:javascript
复制
txSecrect = MD5(12345demo5EA7018A) = 08472000d6fb5b5b2f2dc61583900287

搞清楚了地址的规则和权鉴的原理,就可以获取推拉流相应的地址了。

有两种方式:

  1. 云直播控制台生成(主要用于测试)
  2. 自己拼接生成(用于生产,根据需求来生成)
  3. 控制台生成方式

在「云直播控制台」-「辅助工具」-「地址生成器」,可选择生成推流或者拉流地址,如下:

生成地址

其中,

AppName:应用名,默认为live,根据自己的需求配置。

StreamName :直播通道名字,根据需求填写,比如主播的用户ID。

过期时间 :直播通道的有效时间,过了这个时间,则无法再推送或者拉流。

点击「生成地址」,控制台自动生成好推流和拉流地址和权鉴,如下:

生成推流地址

生成拉流地址

  • 手动拼接方式

我们已经知道了地址的规则,那么自己来拼接也是可以的,而且在生产环境中也只能通过这种方法,因为我们不可能每次开启直播的时候,都要去控制台生成一个地址。

所以,只要按照上面讲解的规则生成地址就可以了,并且地址的生成应该放到我们自己系统的服务器中,而不是 App 中,这样才能避免泄漏权鉴key。

请求流程如下:

App 请求推拉流地址

至此,一个简单的直播系统就搭建完成了。

如果你想要实现一些高级的效果,比如滤镜、添加水印,但是你又不想付费,那也是有办法的,通过 SDK 自定义渲染接口,就可以实现,前提是你有 OpenGL 基础

下面就通过一个灰色滤镜和添加水印,简单介绍一下如何自定义渲染。

4. 自定义视频渲染

目前视频渲染基本都是使用 OpenGL 实现的,腾讯云直播也不例外。

先来看下 SDK 提供的 推流 自定义渲染接口。

TXLivePusher 可以设置一个回调接口 VideoCustomProcessListener ,其接口定义如下:

代码语言:javascript
复制
public interface VideoCustomProcessListener {
/**
* @param textureId: 原视频纹理 ID
* @param width: 视频宽
* @param height: 视频高
* return 新视频画面纹理 ID
*/
int onTextureCustomProcess(int textureId, int width, int height);

void onDetectFacePoints(float[] points);

void onTextureDestoryed();

}

重点是第一个接口 onTextureCustomProcess ,第一个参数是一个 OpenGL 的纹理 ID,这个纹理保存了原视频画面,另外两个参数是画面的宽和高。

返回值也是一个纹理 ID,这个纹理就是经过处理后的视频画面的纹理 ID 。

如果直接把第一个参数 textureId 返回,则渲染的画面就是原始的视频画面。

  • 自定义灰色滤镜

根据上面的接口,既然已经有了原视频画面的纹理,那么对这个纹理进行处理就是很简单的事了。但仅仅处理是不够,还需要返回一个新的纹理,要怎么才能得到一个新的纹理呢?

如果你学过 OpenGL ,以及 OpenGL FBO(帧缓存对象),那就很容易实现了。

OpenGL FBO 提供了缓冲技术,不需要将画面显示出来,可以将画面绘制到一个新的纹理上。详细介绍可以看【OpenGL FBO 数据缓冲区】

限于文章篇幅,以及 OpenGL 相关知识的了解程度,这里只简单将接口实现贴出来,要了解详细的代码,可以参考本文的【Demo源码:GreyFilter】

代码语言:javascript
复制
class VideoPushActivity : AppCompatActivity(), TXLivePusher.VideoCustomProcessListener {
    // 省略无关代码
    // ......
private fun initPusher() {
    val mLivePushConfig = TXLivePushConfig()
    mLivePusher = TXLivePusher(this)

    // 一般情况下不需要修改 config 的默认配置
    mLivePusher.config = mLivePushConfig

    mLivePusher.startCameraPreview(tx_cloud_video_view)

    // 开启自定义渲染
    mLivePusher.setVideoProcessListener(this)
}

private var greyFilter: GreyFilter? = null

override fun onTextureCustomProcess(textureId: Int, width: Int, height: Int): Int {
    if (greyFilter == null) {
        greyFilter = GreyFilter()
        greyFilter?.setTextureID(textureId)
        greyFilter?.setVideoSize(width, height)
    }
    greyFilter?.draw()
    return if(greyFilter?.getFboTextureID() == -1) textureId else greyFilter?.getFboTextureID()!!
}

// ......

}

原理很简单:利用 OpenGL FBO 技术,创建一个新的纹理单元 B ,接着将 SDK 传递给我们的纹理单元 A 中的画面处理成灰色,并绘制到 FBO 上,新的纹理单元 B 就有了灰色滤镜处理过的画面,然后将 B 返回给 SDK 内部进行渲染绘制。

  • 添加水印

添加水印也很简单,水印其实就是在原视频的基础上,绘制多一个 Logo 图片叠加在上面。

那么只需要在 FBO 上面,先绘制原视频数据,再绘制水印即可,唯一要注意的是,需要做好水印的位置和大小的适配。

具体实现参考【Demo源码:WaterMarkDrawer】

五、总结

腾讯云直播 SDK 提供了比较全面的功能,基本上涵盖了直播系统中该有的功能点,整一套使用下来,也比较顺畅易用(仅仅体验使用,深入使用可能会遇到其他的一些问题),主要有以下特点:

  • 系统搭建非常简单,SDK 提供的接口很简洁友好,基本上是傻瓜式接入;
  • 提供的功能点齐全,从采集-本地处理-推流,到云端转码、录制、鉴黄等,再到拉流,还有直播连麦等高级功能,提供了一体式解决方案;
  • 支持推拉流地址自定义,拓展方便;
  • 提供自定义渲染接口,对于有自定义能力的小伙伴来说非常实用。

当然也有美中不足的地方:

  • 拉流自定义渲染接口只提供了原始数据,没有提供 OpenGL 纹理渲染,处理起来比较麻烦;
  • 推流自定义渲染接口必须要返回一个新的纹理,不能直接通过 OpenGL 渲染,需要借助 FBO ,加大了开销;
  • 推拉流都可以设置 SurfaceView 作为渲染窗口,但是被绑定到 SDK 的 OpenGL 线程中,没有暴露渲染上下文,无法定义自己的 OpenGL 渲染环境。文档中也没有深入的说明,目前不知其用途。

综上,直播涉及到的知识确实非常多,投入足够多的精力也并非不可实现,但对于小公司或者没有足够研发投入的公司来说,使用第三方的 SDK 或许是更好的选择,毕竟他们都是顶住了千万级流量的,专业性,稳定性和可靠性都比较好。