什么是 WebRTC

WebRTC(Web Real-Time Communication)是实现浏览器之间点对点实时通讯的一套技术规范(现在也支持 iOS 和 Android 应用)。2010 年 5 月,Google 收购了 VoIP 开发商 Global IP Solutions,在其技术基础上开发了 WebRTC,并于一年后开源。目前,WebRTC 1.0 是 W3C 的标准草案,Chrome 30+ 和 Firefox 36+ 实现了对 WebRTC 的支持

以往,浏览器间的通讯绝大部分还必须要通过服务器中转来间接实现,这需要浏览器和服务器间进行全双工通讯。尽管我们有老的技术(如轮询、Comet),也有新技术(如 WebSocket)来实现,但它们都不是为浏览器间直接通讯而设计的。这些通过服务器中转的方式不可避免地带来了明显的开销,通讯的时延和速度不理想,数据的私密性也难以保证。而 WebRTC 就是专门解决这个问题的。

利用 WebRTC 可以在客户端之间传送任意数据,但它目前最吸引人的应用还是实时视频通讯。多年来,这样的应用都是通过浏览器插件(如 Flash 和 Silverlight)来实现,但 HTML5 的崛起改变了一切,新的 JavaScript API,极大增强了前端代码对硬件的控制能力:GPS、加速度计、陀螺仪、电池、GPU、视频和音频……等等。为了实现视频通讯,我们需要用 getUserMedia,也就是 Stream API,来获取设备的视频和音频设备,具体来讲,就是摄像头和麦克风。目前,只要支持 WebRTC 的浏览器都支持 Stream API

WebRTC 系列文章将一步步讲述一个实时通讯应用是怎样实现的,而本文的内容就是第一步:捕获视频。在本例中,我们将获取摄像头的视频数据,然后显示在页面上一个 <video> 标签中。

简单的开始

首先,我们在页面上放一个 <video> 标签:

<video class="local" autoplay></video>

然后,调用 getUserMedia,它的三个参数如下:

constraints
指定媒体规格(如视频分辨率、帧速率等),Chrome 和 Firefox 上有所不同,见后文
successCallback
成功后的回调函数,返回一个 `MediaStream` 媒体流对象作为参数
errorCallback
失败后的回调函数

获取到媒体流对象后,该怎样把它交给 <video> 标签呢?答案是对象 URL,对象 URL 可以代表某一个 File 对象或 Blob 对象,而我们拿到的 MediaStream 对象就是一个 Blob 对象。利用 URL.createObjectURL 方法来创建 MediaStream 对象的 URL,并填入 <video> 标签的 src 属性就行了。

var localVideo        = document.querySelector("video.local");
var localMediaStream  = null;
var localMediaURL     = null;

function getUserMedia(callback) {
  if (navigator.getUserMedia === undefined) {
    navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
    if (navigator.getUserMedia === undefined) {
      console.error('getUserMedia not supported.')
      return false;
    }
  }
  navigator.getUserMedia({ video: true, audio: true }, callback, function(e) {
    console.error('getUserMedia failed: ' + e.message);
  });
}

getUserMedia(function(stream) {
  localMediaStream = stream;
  localVideo.src = localMediaURL = URL.createObjectURL(localMediaStream);
});

在有摄像头的电脑上,用 Chrome 或 Firefox 打开这个页面。浏览器会询问你是否允许页面访问你的摄像头,点击允许后,页面上就会显示出视频画面。

媒体规格

以上代码中,我们只是简单地指定媒体规格为 { video: true, audio: true },要求浏览器返回视频和音频,而对具体规格没有要求。事实上,如果你摄像头能输出 720p 甚至 1080p 的视频,用以上代码也只能获取到 VGA 规格。所以我们需要对规格做进一步设置。

不幸的是,目前 Chrome 上的实现 webkitGetUserMedia 支持的规格格式遵循一个早期的规范,而不是最新的规范。所以,我们得先判断浏览器类型,再予以指定:

function getMediaConstraints() {
  if (navigator.webkitGetUserMedia) {
    // Chrome
    return {
      video: {
        mandatory: { minWidth: 640, minHeight: 480 },
        optional: [{ minWidth: 1280 }, { minHeight: 720 }, { facingMode: "user" }]
      },
      audio: true
    };
  }
  // Firefox
  return {
    video: {
      width: { min: 640, ideal: 1280 },
      height: { min: 480, ideal: 720 },
      facingMode: "user"
    },
    audio: true
  };
}

使用以上的规格,就可以在摄像头支持时使用 720p 格式,若不支持则回落到 VGA 格式。如果是在手机等移动设备上,则优先选择自拍摄像头。Firefox 38 以前的版本支持也不完全符合标准,不支持 ideal 参数,所以只会返回 VGA 格式。使用这些规格的时候需要谨慎,因为如果设备连你指定的最低标准都达不到,getUserMedia 会失败。

一些润色

你也许在尝试以上例子的时候,会感到有些不对劲,跟平时使用其他聊天软件有点不同。事实上,大部分聊天软件里,你看到自己的画面是左右翻转的,就像镜子一样。因为人们大都习惯照镜子,所以当你看到没有翻转的视频时会觉得别扭,想捋一捋左边的头发却伸出了右手。

其实这个翻转很容易实现,用 CSS3 Transform 就行了:

video {
  &.local {
    transform: rotateY(180deg);
  }
}

你也可以用 CSS3 Filter 给视频添加滤镜效果:

video {
  &.local {
    transform: rotateY(180deg);
  }
  &.grayscale {
    filter: grayscale(1);
  }
  &.sepia {
    filter: sepia(1);
  }
  &.blur {
    filter: blur(5px);
  }
  &.invert {
    filter: invert(.8);
  }
  &.inkwell {
    filter: grayscale(1) brightness(0.45) contrast(1.05);
  }
}

完整例子

下期预告

WebRTC 之点对点连接将会介绍如何在浏览器之间建立点对点连接。