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

本文可以了解到

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

前言

1. 视频时代已经来临

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

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

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

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

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

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

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

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

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

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

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

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

一、直播基础知识

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

1. 基础流程

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

最简单的直播系统

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

流程 详细操作
音视频数据采集 通过摄像头和麦克风采集
音视频滤镜 通过 OpenGLSoundTouch 等工具实现音视频编辑
音视频编码 通过系统硬编码 或 FFmpeg 软编码,将数据编码为 H264AAC
数据封装打包 将编码好的数据封装成指定的格式

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

关于 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 添加:

dependencies {
   implementation 'com.tencent.liteavsdk:LiteAVSDK_Smart:latest.release'
}

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

defaultConfig {
   ndk {
       abiFilters "armeabi", "armeabi-v7a", "arm64-v8a"
   }
}
  • 配置打包参数

在 app/build.gradle 中配置

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 中配置

<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

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // 替换成控制台获取到的 licence url
        val licenceURL = "http://license.vod2.myqcloud.com/license/v1/xxxxx" 
        // 替换成控制台获取到的 licence key
        val licenceKey = "e90e0872aaeb7797xxxxxxxxx" 
        
        TXLiveBase.getInstance().setLicence(this, licenceURL, licenceKey)
    }
}

3. 推流与拉流

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

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

拉流:拉取数据、解码、渲染播放。

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

3.1 实现数据推流

直播 SDK 提供了一个 View 用于视频的播放显示:TXCloudVideoView。把它放到 xml
中即可,SDK 会根据 TXCloudVideoView 的宽高和视频的宽高,做对应的自适应缩放。

// 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">

    <com.tencent.rtmp.ui.TXCloudVideoView
        android:id="@+id/tx_cloud_video_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="推流"
        android:onClick="startPushing"/>
</androidx.constraintlayout.widget.ConstraintLayout>

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

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 = "rtmp://2157.livepush.myqcloud.com/live/demo?txSecret=39193bda8a439ba8852baf9bce1fde39&txTime=5E8DF4FF"
        val ret = mLivePusher.startPusher(rtmpURL.trim())
        if (ret == -5) {
            Log.i("VideoPushActivity", "startRTMPPush: license 校验失败")
        }
    }

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

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

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

  • 通过 mLivePusher.setPushListener(this) 来监听推送状态和网络状态,详见【官方文档】;

  • 通过 TXLivePushConfig 来配置旋转角度、设置水印、帧率、码率等等,非常丰富的参数配置,详见【官方文档】;

  • 通过 mLivePusher.getBeautyManager() 获取到美颜工具 TXBeautyManager 可用于配置美颜相关的内容,不过这个方法是需要另外付费的。

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

3.2 实现拉流播放

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

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

<?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">

    <com.tencent.rtmp.ui.TXCloudVideoView
        android:id="@+id/video_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="播放"
        android:onClick="startPlaying"/>

</androidx.constraintlayout.widget.ConstraintLayout>

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

// 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 = "http://2157.liveplay.myqcloud.com/live/demo.flv"
        mLivePlayer.startPlay(flvUrl, TXLivePlayer.PLAY_TYPE_LIVE_FLV)
    }
}

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

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

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 的计费方式恰恰就是根据播放流量来计费的,如果被人家随便拉流播放的话,必然会造成损失。

// 无权鉴推流地址
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 的加密值进行验证。泄漏以后要赶紧更新。

  • 权鉴计算

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

txSecrect=Md5(key+StreamName+txTime)&txTime=xxxx

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

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

因此,

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,那么

txSecrect = MD5(12345demo5EA7018A) = 08472000d6fb5b5b2f2dc61583900287

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

有两种方式:

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

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

生成地址

其中,

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

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

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

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

生成推流地址
生成拉流地址
  • 手动拼接方式

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

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

请求流程如下:

App 请求推拉流地址

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

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

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

4. 自定义视频渲染

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

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

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

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

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 或许是更好的选择,毕竟他们都是顶住了千万级流量的,专业性,稳定性和可靠性都比较好。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容