65.9K
CodeProject 正在变化。 阅读更多。
Home

Android 应用与浏览器之间的 WebRTC 视频聊天

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2017年5月4日

CPOL

11分钟阅读

viewsIcon

23248

在这里,我们使用 WebRTC 流引擎在原生 Android 应用和 Web 浏览器 Google Chrome 或 Firefox 之间建立 WebRTC 连接。

videochat_android_browser

自 WebRTC 公开以来,视频聊天变得更容易开发。为此涌现了许多 API、服务、服务器和框架。在本文中,我们将详细介绍一种开发 Web 浏览器与原生 Android 应用程序之间视频聊天的方法。

浏览器中的视频聊天

浏览器之间的经典 WebRTC 视频聊天始于 SDP (会话描述协议) 交换。**Alice** 将其 SDP 发送给 **Boris**,Boris 再回复他的 SDP。SDP 是一个类似以下的配置

o=- 1468792194439919690 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE audio video
a=msid-semantic: WMS 9nKsWmxMvOQBYaz9xhRffWeWSUbCbnox6aQ4
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:kSrQ
a=ice-pwd:4uIyZd/mbVSVe2iMFgC1i3qn
a=fingerprint:sha-256 6B:29:2F:47:EB:38:64:F3:25:CE:BD:E6:B0:3F:A6:FA:55:57:A9:EA:44:0B:7C:45:D2:0D:F4:96:8D:B2:9F:BA
a=setup:actpass
a=mid:audio
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=sendonly
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:112 telephone-event/32000
a=rtpmap:113 telephone-event/16000
a=rtpmap:126 telephone-event/8000
a=ssrc:3525514540 cname:drYey7idt605CcEG
a=ssrc:3525514540 msid:9nKsWmxMvOQBYaz9xhRffWeWSUbCbnox6aQ4 09bdb6e7-b4b3-437b-945e-771f535052e3
a=ssrc:3525514540 mslabel:9nKsWmxMvOQBYaz9xhRffWeWSUbCbnox6aQ4
a=ssrc:3525514540 label:09bdb6e7-b4b3-437b-945e-771f535052e3
m=video 9 UDP/TLS/RTP/SAVPF 96 98 100 102 127 97 99 101 125
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:kSrQ
a=ice-pwd:4uIyZd/mbVSVe2iMFgC1i3qn
a=fingerprint:sha-256 6B:29:2F:47:EB:38:64:F3:25:CE:BD:E6:B0:3F:A6:FA:55:57:A9:EA:44:0B:7C:45:D2:0D:F4:96:8D:B2:9F:BA
a=setup:actpass
a=mid:video
a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:4 urn:3gpp:video-orientation
a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=sendonly
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtpmap:100 H264/90000
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:102 red/90000
a=rtpmap:127 ulpfec/90000
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:125 rtx/90000
a=fmtp:125 apt=102
a=ssrc-group:FID 2470936840 2969787502
a=ssrc:2470936840 cname:drYey7idt605CcEG
a=ssrc:2470936840 msid:9nKsWmxMvOQBYaz9xhRffWeWSUbCbnox6aQ4 ce9235c5-f300-466a-aadd-b969dc2f3664
a=ssrc:2470936840 mslabel:9nKsWmxMvOQBYaz9xhRffWeWSUbCbnox6aQ4
a=ssrc:2470936840 label:ce9235c5-f300-466a-aadd-b969dc2f3664
a=ssrc:2969787502 cname:drYey7idt605CcEG
a=ssrc:2969787502 msid:9nKsWmxMvOQBYaz9xhRffWeWSUbCbnox6aQ4 ce9235c5-f300-466a-aadd-b969dc2f3664
a=ssrc:2969787502 mslabel:9nKsWmxMvOQBYaz9xhRffWeWSUbCbnox6aQ4
a=ssrc:2969787502 label:ce9235c5-f300-466a-aadd-b969dc2f3664

从这个 SDP 配置中,我们可以看出,它建议使用 **H.264** 和 **VP8** 编解码器进行视频,以及 **Opus** 进行音频。此外,它还提供了大量其他用于通信的信息:编解码器优先级、**fir**、**nack**、**pli** 反馈的使用、**H.264** 编解码器的配置文件级别为 42e01f - Baseline 3.1 等等。

当您使用原生的 **WebRTC API** 实现视频聊天时,您需要理解 SDP、candidate、codec、ICE、STUN、TURN 等以及其他许多听起来令人生畏的术语。

video_chat_in_browser

WebRTC、Websockets 和 SIP

WebRTC 和 Websockets 这两个术语经常被混淆。有时 SIP 也会卷入其中。

Web_RTC_Websockets

好吧,我们可以肯定地说,**WebRTC 与 Websockets 或 SIP 都无关**。

**Websockets** 只是一个方便地将 **SDP** 从 Boris 传输到 Alice 的方法。我们可以使用普通的 **HTTP** 来实现,或者通过电子邮件发送 SDP。SDP 消息交换是信令信息,我们可以使用任何协议来发送它。对于浏览器,默认的发送数据协议是 Websockets 和 HTTP。因此,Websockets 主要被使用,因为它比 HTTP 更接近实时。您无法通过 Websockets 传输视频或音频,只能传输信令信息:文本和命令。

Web_RTC_SIP

**SIP** 是一个用于交换消息的文本协议。WebRTC 在浏览器中经常被错误地称为 **SIP**,很可能是因为 SIP 消息也使用 SDP 来配置编解码器和建立连接。

另一方面,当我们说诸如 **SIP 电话** 之类的话时,我们指的是一种设备,它除了支持 **SIP (RFC3261)** 协议外,还支持十几种其他网络规范和协议:RTP、SDP、AVPF 等。

确实,其核心方面,WebRTC 使用与 SIP 电话(SRTP、STUN 等)相同的构建块。因此,可以说 WebRTC 和 SIP 设备及软件都使用相同的技术基础。但称浏览器中的 WebRTC 为 SIP 是不正确的,这至少是因为浏览器默认没有 SIP 功能。

Web_RTC_Websockets_SIP

**WebRTC** 是一项技术,具有三个主要的音视频传输功能:

  • 捕获、编码和发送
  • 接收、解码和播放
  • 克服 NAT 和防火墙

此外还有许多辅助功能,如抖动补偿、自适应比特率、网络过载控制等。

如上所述,为了通过 WebRTC 成功传输媒体,Alice 和 Boris 应该交换包含视频流格式、打包和其他参数的详细信息的 SDP,这些参数指定了 SDP 发送者将如何接收视频。

除了交换 SDP 之外,可能还需要 **TURN 服务器**。如果无法建立 peer-to-peer 连接(例如,如果 Alice 或 Boris 具有某种不友好的 NAT,例如 **对称** NAT),该服务器将转发视频流量。

现在,假设我们想为聊天添加第三个活跃参与者,或者仅仅是一个观看者。这里有一个很好的例子:辩论。两个参与者交谈,而其他人只是观看。另一个例子是三人或多人聊天。

当第三个参与者加入时,情况会变得更复杂。现在每个参与者都需要**捕获和压缩** **两个视频流** 而不是一个,并**建立相互连接**以克服 **NAT**。在这种情况下,建立连接所需的时间会增加,而连接的稳定性会降低。同时压缩和发送两个或多个视频流会对 CPU 和网络造成严重负载,并影响质量,尤其是在移动设备上。

Web_RTC_Websockets_SIP

像这样的任务:

  • 连接三个或更多参与者
  • 连接视频聊天的附加订阅者
  • 录制视频聊天

超出了 peer-to-peer 的范围,需要一个集中的 WebRTC 服务器来管理所有连接。

Web_RTC_server

正如我们之前所说,有一些服务和服务器,以及或多或少方便的 WebRTC API 之上的 API,可以加快视频聊天开发,并允许使用更方便的抽象,即 **Stream**、**Room**、**Publisher**、**Subscriber** 等。

例如,要创建最简单的视频聊天,仅交换流的名称就足够了。Boris 知道 Alice 的流。Alice 知道 Boris 的流。视频聊天就准备好了。

server_streamname

浏览器中视频聊天的示例

在本文中,我们将演示 Streaming API 如何与 **Web Call Server 5** 一起工作,Web Call Server 是一个用于视频聊天和在线广播的 WebRTC 服务器。

视频聊天在操作中的演示如图所示的两个截图。第一个订阅者 **Alice** 看到视频聊天如下:

video_chat

第二个订阅者 **Edward** 看到视频聊天如下:

video_chat_in_browser

在此示例中,发生了几件事:

  1. Alice 从浏览器向服务器发送名为 Alice 的视频流。
  2. Edward 从浏览器向服务器发送名为 Edward 的视频流。
  3. Alice 获取并播放了名为 Edward 的视频流。
  4. Edward 获取并播放了名为 Alice 的视频流。

从示例中可以看出,我们构建视频聊天的基础是 Alice 和 Edward 都知道对方的流名称。我们没有直接使用 SDP、PeerConnection、NAT、TURN 等。

因此,**通过简单地将流的名称传递给应该播放它们的人,就可以实现视频聊天。**

这个简单的概念允许使用任何前端和后端技术,如 Jquery、Bootstrap、React、Angular、PHP、Java、.Net 等。好消息是,嵌入视频流和视频聊天支持不会影响现有的 Web 应用程序。您可以通过允许(或拒绝)给定订阅者播放特定视频流来控制您的视频聊天。

浏览器中视频聊天的源代码

现在让我们看看相应的代码。带有视频聊天的 HTML 页面有两个主要的 **div 元素**:

  • localVideo - 从网络摄像头捕获的视频
  • remoteVideo - 从服务器播放的视频

source_code_of_video_chat

您可以为这些 div 分配任意标识符,例如 id="captureVideo" 或 id="playbackVideo",但页面上必须同时存在这两个 div 元素。

带有 **localVideo** 和 **remoteVideo** 块的 HTML 页面如下所示:

<html>
<head>
    <script language="javascript" src="flashphoner.js"></script>
    <script language="javascript" src="video-chat.js"></script>
</head>
<body onLoad="init()">
<h1>Video Chat</h1>
<div id="localVideo" style="width:320px;height:240px;border: 1px solid"></div>
<div id="remoteVideo" style="width:320px;height:240px;border: 1px solid"></div>
<input type="button" value="connect" onClick="connect()"/>
<input type="button" value="publish" onClick="publish('Alice')"/>
<input type="button" value="play" onClick="play('Edward')"/>
<p id="status"></p>
</body>
</html>

现在,这是负责发送和播放视频的代码。

从网络摄像头发送流

要发送,我们使用 **session.createStream().publish()** API 方法。对于这个流,我们指定应该显示从网络摄像头捕获的视频的 HTML div 元素 **localVideo**,以及视频流的名称 **Alice**,这样任何连接的客户端知道此名称都可以播放该流。

session.createStream({
        name: "Alice",
        display: localVideo,
        cacheLocalResources: true,
        receiveVideo: false,
        receiveAudio: false
    }).on(Flashphoner.constants.STREAM_STATUS.PUBLISHING, function (publishStream) {
        setStatus(Flashphoner.constants.STREAM_STATUS.PUBLISHING);
    }).on(Flashphoner.constants.STREAM_STATUS.UNPUBLISHED, function () {
        setStatus(Flashphoner.constants.STREAM_STATUS.UNPUBLISHED);
    }).on(Flashphoner.constants.STREAM_STATUS.FAILED, function () {
        setStatus(Flashphoner.constants.STREAM_STATUS.FAILED);
    }).publish();

从服务器播放流

要播放,我们指定要播放的流的名称,以及应该显示从服务器接收的流的 HTML div 元素 **remoteVideo**。我们使用 **session.createStream().play()** API 方法。

 session.createStream({
        name: "Edward",
        display: remoteVideo,
        cacheLocalResources: true,
        receiveVideo: true,
        receiveAudio: true
    }).on(Flashphoner.constants.STREAM_STATUS.PLAYING, function (playStream) {
        setStatus(Flashphoner.constants.STREAM_STATUS.PLAYING);
    }).on(Flashphoner.constants.STREAM_STATUS.STOPPED, function () {
        setStatus(Flashphoner.constants.STREAM_STATUS.STOPPED);
    }).on(Flashphoner.constants.STREAM_STATUS.FAILED, function () {
        setStatus(Flashphoner.constants.STREAM_STATUS.FAILED);
    }).play();

在使用服务器时,HTML 页面将从服务器接收各种状态,例如播放的 **PLAYING**、**STOPPED**,以及发布的 **PUBLISHING**、**UNPUBLISHED**。因此,视频聊天工作所需的基本操作是在网页上放置两个 div 块,并包含执行给定流名称的 stream.play() 和 stream.publish() 的相应脚本。**Two Way Streaming** 示例的完整源代码可以在 此处 下载。

 

Android 应用程序中 WebRTC 视频聊天的示例

Android 的视频聊天工作方式与浏览器中的视频聊天完全相同。该应用程序连接到服务器,并发送来自 Android 设备摄像头的视频流,以及从服务器接收并播放其他视频流。下面是 Android 应用 **Streaming Min**(浏览器中视频聊天 Two Way Streaming 示例的移动版本),它可以实现视频流的交换。video_chat_in_android 从屏幕截图中可以看出,没有变化。我们有两个视频窗口。左边的窗口显示从网络摄像头捕获的视频,右边的窗口显示从服务器接收的视频。视频流的交换也基于流名称。我们发布一个流并播放另一个流。

Android 应用程序视频聊天的源代码

虽然我们在浏览器中创建视频聊天时使用了包含 **flashphoner.js** API 脚本的 **Web SDK**,但对于功能齐全的 Android 应用程序,我们需要将 Android SDK 的 **aar 文件** 导入到项目中。要理解这一点是如何工作的,我们建议构建并执行基于 Android SDK 的 Streaming Min 示例。所有示例都可以在 github 仓库 中找到。

1. 下载所有示例

git clone https://github.com/flashphoner/wcs-android-sdk-samples.git

2. 下载 SDK

wget https://flashphoner.com/downloads/builds/flashphoner_client/wcs-android-sdk/aar/wcs-android-sdk-1.0.1.25.aar

3. 将 SDK 作为 aar 文件链接到示例。

cd export
./export.sh /tmp/wcs-android-sdk-1.0.1.25.aar

注意,我们将 export.sh 脚本指向下载的文件:**wcs-android-sdk-1.0.1.25.aar** - Android SDK

结果,在 **export/output** 文件夹中,您将找到一个完全配置好的项目,可以在 **Android Studio** 中打开。

现在您只需要使用 **gradle** 构建示例。

1 - 创建新的运行配置

source_code_android_application

2 - 选择 Gradle 脚本

source_code_of_video_chat

3 - 构建

video_chat_for_android

结果,我们将获得 **apk 文件**,可以安装到 Android 设备上。在此示例中,我们与浏览器交换了视频流。视频流 **test33** 从 Android 设备发送到服务器并在浏览器中播放。视频流 **8880** 由浏览器发送并在 Android 设备上播放。因此,我们实现了浏览器和 Android 应用之间的双向音视频通信。

video_chat_android_application

在视频聊天的 Web 版本中,我们使用了 HTML div 元素来显示视频。在 Android 上,我们使用渲染器。

private SurfaceViewRenderer localRender;
private SurfaceViewRenderer remoteRender;

div_elements_for_video

**localRenderer** 显示从 Android 设备摄像头捕获的视频。**remoteRenderer** 显示从服务器接收的视频。

1. 连接到服务器并设置渲染器。

sessionOptions = new SessionOptions(mWcsUrlView.getText().toString());
sessionOptions.setLocalRenderer(localRender);
sessionOptions.setRemoteRenderer(remoteRender);
...
session = Flashphoner.createSession(sessionOptions);
…
session.connect(new Connection());

2. 创建一个具有任意名称的流,并将该流发布到服务器。

StreamOptions streamOptions = new StreamOptions(mPublishStreamView.getText().toString());
publishStream = session.createStream(streamOptions);
...
publishStream.publish();

3. 指定要播放的流的名称,并从服务器获取该流。

StreamOptions streamOptions = new StreamOptions(mPlayStreamView.getText().toString());
playStream = session.createStream(streamOptions);
...
playStream.play();

StreamOptions streamOptions = new StreamOptions(mPlayStreamView.getText().toString()); playStream = session.createStream(streamOptions); ... playStream.play(); 完整的 StreamingMinActivity.java 类源代码 在此处 提供。整个 Android 的 Streaming Min 示例可在 此处 的仓库中找到。

Web Call Server

总之,我们演示了如何在浏览器中的 HTML 页面和 Android 应用程序之间创建简单的视频流交换。视频流通过 Web Call Server,它既是信令服务器,也是音频和视频代理。

web_call_server Web Call Server - 是一款可以安装在 Linux 上、虚拟机或专用服务器上的**服务器软件**。WCS 是一个**流媒体 WebRTC 服务器**,它可以管理来自浏览器、iOS 和 Android 设备的视频流。

参考文献

技术和协议

WebRTC - WebRTC 技术
SDP - 会话描述协议, RFC
Websocket - Websocket 协议, RFC

用于视频聊天开发的服务器和 API

Web Call Server - 用于视频聊天的 WebRTC 流媒体服务器

© . All rights reserved.