视频直播终端开发之微信小程序版
前言
由于项目需要最近接到公司的一个研发任务,尝试开发视频直播功能,要求双方可以对讲互动,并提供微信小程序、PC、Web等版本。由于之前对流媒体技术有所积累,这个任务只要满足功能演示,因此这个任务对我来说还不是太困难。技术人员要对自身掌握的技术要不断更新,技术的积累不要仅满足于工作,更要拓展自身的技术视野,不断去尝试哪些新技术。只有这样在将来的工作中才不至于被突入其来的新任务为难。
技术
视频直播其实就是把基于硬件产生的模拟信号数据化的过程,但涉及的技术方面比较广,我也只是学习了一些皮毛,在这里简单介绍一下,视频直播其它主要包括音频和视频的数据整合,单向的流媒体数据传输过程主要如下六大过程。详见“视频直播技术随笔”文章中有详细说明。
1. 音视频数据采集
主要是通过终端硬件设备产生原始音视频数据模拟信号,如手机、摄像头、麦克等,现在主流音频数据主要是PCM格式数据,视频数据主要是YUV格式的数据,由于这些数据是直接由硬件设备产生,对硬件设备有天然的高契合度。此外还需重点关注数据的“采样率”、“位宽”、“声道”和“帧”等参数。
2. 数据加工处理
主要针对已经过采样的音视频数据在编码前需要进行必要的优化处理,这此阶段要对音视频数据进行降噪处理,原始的PCM和YUV数据中由受环境、电路、辐射等影响产生大量的冗余噪点。这些冗余数据使严重影响音视频的质量。因此我们在音视频数据采集完成后要进行二次工作处理。
视频:降噪、水印、滤镜等
音频:降噪、混音、特效等
3. 流媒体数据编码
音视频二次加工处理完成以后就需要进行编码操作了,编码其它就是数据压缩,为了数据可以在网络中快速传播,我们要想办法把数据体积压缩到最小。
视频:H.264、H.2645、VP8、VP9等格式,主流使用H.264。
音频:SPEEX AAC, OPUS, G.711等格式,主流使用AAC。
4. 数据网络传输
数据压缩完成以后要进行网络传输,对实时性要求不高的应用视频直播平台一般都使用RTMP协议进行数据的传输,RTMP是在TCP之上的网络协议。对于实时互动要求高的视频直播则必须使用UDP进行数据传输。
5. 流媒体数据解码
就是将对编码数据做反向操作。如音频是AAC编码,则它再解为PCM格式数据。视频是H.264再解为YUV数据。
6. 音视频渲染播放
对于音频直接将PCM数据放入到音频驱动缓冲驱,驱动程序就会将音频播放出来。对于视频一般会通过 opengl利用 GPU进行图像渲染。
实现
微信小程序版
微信小程序提供了2个组件live-pusher以及live-player来完成对音视频的数据推送和拉取等操作,但是因为官方文档过于简略,以至于很多人难以上手。
视频流推送 live-pusher
微信小程序通过live-pusher标签将手机麦克和摄像头产生的媒体数据通过RTMP网络协议向媒体服务器端推送数据,详细参数说明可以查阅微信开放文档。
视频流拉取 live-player
微信小程序通过live-player标签采用RTMP网络协议从指定媒体服务器拉取媒体流数据进行渲染,详细参数说明可以查阅微信开放文档。
界面设计
模仿微信视频电话的界面,实现点对点的视频对讲功能。
pusher.wxml的代码如下:
<view class="page-body">
<view class="page-section tc">
<!-- 拉流-->
<live-player id="player" src="{{playerUrl}}" mode="RTC" autoplay object-fit="fillCrop" bindstatechange="onPlayerStateChange" bindnetstatus="onPlayerNetstatusChange" binderror="onError" wx:if="{{isplayerexit}}" >
<cover-view class="btn-area">
<cover-view bindtap='bindMute' class="page-body-button btn-mute">
<cover-image src="../../images/mute{{muteStatus}}.png" class="img-mute"></cover-image>
</cover-view>
<cover-view bindtap='bindStop' class="page-body-button btn-stop">挂断</cover-view>
<cover-view bindtap="bindSwitchCamera" class="page-body-button btn-camera">
<cover-image src="../../images/camera.png" class="img-camera"></cover-image>
</cover-view>
</cover-view>
</live-player>
<!-- 推流-->
<live-pusher id="pusher" url="{{pusherUrl}}" mode="RTC" autopush bindstatechange="onPusherStateChange" wx:if='{{ispusherexit}}' style="width:{{pusherwidth}};height:{{pusherheight}}">
<cover-view class="btn-area" wx:if="{{isIconShow}}">
<cover-view bindtap='bindStop' class="page-body-button btn-stop">挂断</cover-view>
</cover-view>
<cover-view class="fullscreen" style="top:{{top}};right:{{right}}"></cover-view>
</live-pusher>
</view>
</view>
上面代码所示live-player 中mode属性指明要将声音与视频数据一起推送到src绑定的服务器地址。设置autoplay可以地界面加载完成后自动开始数据推送操作。
pusher.js 业务逻辑代码如下:
var room = require("../../service/roomService.js");
var videoStream = require("../../component/videoStream.js");
const string = require("../../utils/string.js");
const app = getApp()
Page({
wxPusher: null, ///推流器
wxPlayer: null, ///拉流器
hasPerson: null,
otherRoom: '',
data: {
ispusherexit: true, //推流是否存在
pusherUrl: '', //推流地址
isplayerexit: true, //拉流是否存在
playerUrl: '', //拉流地址
pusherwidth: '100px',
pusherheight: '100px',
isIconShow: true,
top: '5px',
right: '5px',
muteStatus: '1'
},
onReady(res) {
this.wxPusher = wx.createLivePusherContext('pusher')
this.wxPlayer = wx.createLivePlayerContext('player');
this.setData({
pusherUrl: videoStream.pusher.url(),
});
},
onLoad(option) {
this.otherRoom = option.roomid;
string.isEmpty(this.otherRoom, this.getSelfRommPerson, roomid => {
console.log("进入其它直播间");
this.setData({
playerUrl: videoStream.player.url(roomid)
});
});
},
getSelfRommPerson: function () {
var that = this;
this.hasPerson = setInterval(function () {
room.otherPerson(person => {
that.setData({
playerUrl: videoStream.player.url(person.id)
});
clearInterval(that.hasPerson);
});
}, 1000);
},
//点击左上角的返回时调用
onUnload() {
string.isEmpty(this.otherRoom, s=>{
room.delete();
}, s => {
room.removeSelf(s);
});
},
onPlayerNetstatusChange: function (e) {
videoStream.player.event.netstatus(e);
},
onPlayerStateChange: function (e) {
videoStream.player.event.statechange(e);
},
onPusherStateChange: function (e) {
videoStream.pusher.event.statechange(e);
},
onError: function (e) {
videoStream.error(e);
},
//停止推流
bindStop(e) {
console.log("停止推流");
this.wxPusher.stop()
wx.reLaunch({
url: '/pages/index/index'
})
},
//转换摄像头
bindSwitchCamera() {
this.wxPusher.switchCamera()
},
//拉流静音
bindMute() {
if (this.data.muteStatus == '1') {
this.setData({
muteStatus: '0'
})
} else {
this.setData({
muteStatus: '1'
})
}
this.wxPlayer.mute();
}
})
运行结果
总结
通过测试微信小程序对RTMP支持还是很给力的,点对点视频流推送延迟也做了细致优化,基本可以满足实际项目中的应用。