一.WebRTC的Android客户端搭建

1.libjingle_peerconnection_so.so

2.libjingle_peerconnection.jar

3.客户端源码一份(可以在github上下载)

二、相关概念介绍

1.P2P:点对点通讯;

2.STUN:提供反射地址使双方可以进行P2P通讯;

3.TURN:在反射地址方式失败情况下的补充方案,即使用中继器,使双方百分之百能够通讯;

4.ICE:综合STUN与TURN两种方案,找出一种最合理最廉价的可行路径;

5.SIP/SDP:SIP一种音视频通讯的协议,SDP为SIP协议中对音视频的描述;

6.PeerConnectionFactory/PeerConnection:整个WebRTC中最核心的类,有了这个类才能获得音视频相关的其他操作,

7.MediaStream:流媒体,包括音频和视频;

8.Track:流媒体中的轨,封装了流媒体的数据,包括音频数据中的声道,视频数据中的YUV场;

9.Session Management:抽象的会话层;

10.RTP:流媒体协议;

11.iSAC:音频语音编码器;

12.iLBC:音频语音解码器;

13.VP8:视频图像编解码器;

14.Room:在web客户端会生成一个随机的房间号,在android客户端需要输入该房间号进行相互连接进行通讯;

15.Observe:观察连接方的数据,就是一个连接后数据的回调接口;

三、相关类的介绍

1.AppRTCAudioManager:音频管理类,全部调用Android SDK已有的方法;

2.AppRTCClient:自定义的与服务端进行通讯和消息回调的接口;

3.AppRTCProximitySensor:Android手机上的距离传感器,使手机靠近耳朵时让按键失效;

4.CallActivity:建立通讯连接,并且显示音视频的界面;

5.CallFragment:通讯界面,CallActivity的子界面;

6.CaptureQualityController:控制界面的画质分辨率大小,用seekbar显示;

7.ConnectActivity:初始化相机和通信的一些参数,点击呼叫按钮时跳转到CallActivity界面进行呼叫;

8.CpuMonitor:显示CPU的当前使用性能数据;

9.HudFragment:参数的界面显示,包括上行宽带和丢包率;

10.PeerConnectionClient:PeerConnection的实现,有了这个类才能进行音视频相关数据通讯;

11.PercentFrameLayout:封装了FrameLayout;

12.RoomParametersFetcher:解析服务器的json数据,并转发房间的参数和地址;

13.SettingsActivity:设置Activity;

14.SettingsFragment:设置Fragment,一些音视频通讯的参数设置;

15.UnhandledExceptionHandler:crash时抓异常,不会直接抛出异常;

16.WebSocketChannelClient:websockot封装;

17.WebSocketRTCClient:对WebSocketChannelClient使用,进行websockt方式发送数据;

工具类:

1.AppRTCUtils:

2.AsyncHttpURLConnection:

3.LooperExecutor:

四、WebSocket

如果下载的代码中含有autobanh.jar这个jar包,那么可以断定该Android客户端与WebRTC的通讯是采用WebSocket的方式进行通讯的,其实现主要在WebSocketChannelClient和WebSocketRTCClient这两个类中,其回调接口则在WebSocketConnectionObserver的三个回调函数里面,这个类被封装在了WebSocketChannelClient里面。(WebSocket的详细知识不做介绍)

五、WebRTC呼叫流程介绍(转载)

上述序列中,标注的场景是ClientA向ClientB发起对聊请求,调用描述如下:

·ClientA首先创建PeerConnection对象,然后打开本地音视频设备,将音视频数据封装成MediaStream添加到PeerConnection中。

·ClientA调用PeerConnection的CreateOffer方法创建一个用于offer的SDP对象,SDP对象中保存当前音视频的相关参数。ClientA通过PeerConnection的SetLocalDescription方法将该SDP对象保存起来,并通过Signal服务器发送给ClientB。

·ClientB接收到ClientA发送过的offer SDP对象,通过PeerConnection的SetRemoteDescription方法将其保存起来,并调用PeerConnection的CreateAnswer方法创建一个应答的SDP对象,通过PeerConnection的SetLocalDescription的方法保存该应答SDP对象并将它通过Signal服务器发送给ClientA。

·ClientA接收到ClientB发送过来的应答SDP对象,将其通过PeerConnection的SetRemoteDescription方法保存起来。

·在SDP信息的offer/answer流程中,ClientA和ClientB已经根据SDP信息创建好相应的音频Channel和视频Channel并开启Candidate数据的收集,Candidate数据可以简单地理解成Client端的IP地址信息(本地IP地址、公网IP地址、Relay服务端分配的地址)。

·当ClientA收集到Candidate信息后,PeerConnection会通过OnIceCandidate接口给ClientA发送通知,ClientA将收到的Candidate信息通过Signal服务器发送给ClientB,ClientB通过PeerConnection的AddIceCandidate方法保存起来。同样的操作ClientB对ClientA再来一次。

·这样ClientA和ClientB就已经建立了音视频传输的P2P通道,ClientB接收到ClientA传送过来的音视频流,会通过PeerConnection的OnAddStream回调接口返回一个标识ClientA端音视频流的MediaStream对象,在ClientB端渲染出来即可。同样操作也适应ClientB到ClientA的音视频流的传输。

六、回调函数

1.WebSocket回调接口与主要消息处理

Candidate、answer、offer、bye四大类消息

private class WebSocketObserverimplements WebSocketConnectionObserver {

@Override

public void onOpen() {

Log.d(TAG, "WebSocket connection opened to: " + wsServerUrl);

executor.execute(new Runnable() {

@Override

public void run() {

state = WebSocketConnectionState.CONNECTED;

// Check if we have pending register request.

if (roomID !=null && clientID != null) {

register(roomID, clientID);

}

}

});

}

@Override

public void onClose(WebSocketCloseNotification code, String reason) {

Log.d(TAG, "WebSocket connection closed. Code: " + code

+ ". Reason: " + reason + ". State: " + state);

synchronized (closeEventLock) {

closeEvent = true;

closeEventLock.notify();

}

executor.execute(new Runnable() {

@Override

public void run() {

if (state != WebSocketConnectionState.CLOSED) {

state = WebSocketConnectionState.CLOSED;

events.onWebSocketClose();

}

}

});

}

@Override

public void onTextMessage(String payload) {

Log.d(TAG, "WSS->C: " + payload);

final String message = payload;

executor.execute(new Runnable() {

@Override

public void run() {

if (state == WebSocketConnectionState.CONNECTED

|| state == WebSocketConnectionState.REGISTERED) {

events.onWebSocketMessage(message);

}

}

});

}

@Override

public void onRawTextMessage(byte[] payload) {

}

@Override

public void onBinaryMessage(byte[] payload) {

}

}

@Override

public void onWebSocketMessage(final String msg) {

if (wsClient.getState() != WebSocketConnectionState.REGISTERED) {

Log.e(TAG, "Got WebSocket message in non registered state.");

return;

}

try {

JSONObject json = new JSONObject(msg);

String msgText = json.getString("msg");

String errorText = json.optString("error");

if (msgText.length() > 0) {

json = new JSONObject(msgText);

String type = json.optString("type");

if (type.equals("candidate")) {

IceCandidate candidate = new IceCandidate(

json.getString("id"),

json.getInt("label"),

json.getString("candidate"));

events.onRemoteIceCandidate(candidate);

else if (type.equals("answer")) {

if (initiator) {

SessionDescription sdp = new SessionDescription(

SessionDescription.Type.fromCanonicalForm(type),

json.getString("sdp"));

events.onRemoteDescription(sdp);

else {

reportError("Received answer for call initiator: " + msg);

}

else if (type.equals("offer")) {

if (!initiator) {

SessionDescription sdp = new SessionDescription(

SessionDescription.Type.fromCanonicalForm(type),

json.getString("sdp"));

events.onRemoteDescription(sdp);

else {

reportError("Received offer for call receiver: " + msg);

}

else if (type.equals("bye")) {

events.onChannelClose();

else {

reportError("Unexpected WebSocket message: " + msg);

}

else {

if (errorText !=null && errorText.length() > 0) {

reportError("WebSocket error message: " + errorText);

else {

reportError("Unexpected WebSocket message: " + msg);

}

}

catch (JSONException e) {

reportError("WebSocket message JSON parsing error: " + e.toString());

}

}

2.Observer接口

主要是连接建立完成后Ice的改变和流信息的改变引起的回调

public static interface Observer {

/** Triggered when the SignalingState changes. */

public void onSignalingChange(SignalingState newState);

/** Triggered when the IceConnectionState changes. */

public void onIceConnectionChange(IceConnectionState newState);

/** Triggered when the ICE connection receiving status changes. */

public void onIceConnectionReceivingChange(boolean receiving);

/** Triggered when the IceGatheringState changes. */

public void onIceGatheringChange(IceGatheringState newState);

/** Triggered when a new ICE candidate has been found. */

public void onIceCandidate(IceCandidate candidate);

/** Triggered when media is received on a new stream from remote peer. */

public void onAddStream(MediaStream stream);

/** Triggered when a remote peer close a stream. */

public void onRemoveStream(MediaStream stream);

/** Triggered when a remote peer opens a DataChannel. */

public void onDataChannel(DataChannel dataChannel);

/** Triggered when renegotiation is necessary. */

public void onRenegotiationNeeded();

}

3.SDP接口

主要是连接建立的过程中引起的回调

/** Interface for observing SDP-related events. */

public interface SdpObserver {

/** Called on success of Create{Offer,Answer}(). */

public void onCreateSuccess(SessionDescription sdp);

/** Called on success of Set{Local,Remote}Description(). */

public void onSetSuccess();

/** Called on error of Create{Offer,Answer}(). */

public void onCreateFailure(String error);

/** Called on error of Set{Local,Remote}Description(). */

public void onSetFailure(String error);

}

4.PeerConnectionClient

生成PeerConnection,实现相关的回调,完成整个业务逻辑

private final PCObserverpcObserver = new PCObserver();(Observer )

private final SDPObserversdpObserver = new SDPObserver();(SdpObserver)

private PeerConnectionFactoryfactory;

private PeerConnectionpeerConnection;

5.CallActivity

private PeerConnectionClientpeerConnectionClient = null;

private AppRTCClientappRtcClient;

七、Native函数之信令协商

6.1 加载so文件

static {

System.loadLibrary("jingle_peerconnection_so");

}

6.2 PeerConnectionFactory相关Native函数

6.2.1网络接口相关参数

public static class Options {

// Keep in sync with webrtc/base/network.h!

static final int ADAPTER_TYPE_UNKNOWN = 0;

static final int ADAPTER_TYPE_ETHERNET = 1 << 0;

static final int ADAPTER_TYPE_WIFI = 1 << 1;

static final int ADAPTER_TYPE_CELLULAR = 1 << 2;

static final int ADAPTER_TYPE_VPN = 1 << 3;

static final int ADAPTER_TYPE_LOOPBACK = 1 << 4;

public int networkIgnoreMask;

public boolean disableEncryption;

}

6.2.2初始化PeerConnectionFactory

// |context| is an android.content.Context object, but we keep it untyped here

// to allow building on non-Android platforms.

// Callers may specify either |initializeAudio| or |initializeVideo| as false

// to skip initializing the respective engine (and avoid the need for the

// respective permissions).

// |renderEGLContext| can be provided to suport HW video decoding to

// texture and will be used to create a shared EGL context on video

// decoding thread.

public static native boolean initializeAndroidGlobals(Object context, boolean initializeAudio,boolean initializeVideo,boolean videoHwAcceleration);

Context:简单的ApplicationContext,或者其他Context相关的上下文。

initializeAudio:初始化音频部分。(boolean)

videoHwAcceleration:是否启用硬件加速。(boolean)

6.2.3初始化音视频轨

private static final String FIELD_TRIAL_VP9 = "WebRTC-SupportVP9/Enabled/";

// Field trial initialization. Must be called before PeerConnectionFactory

// is created.

public static native void initializeFieldTrials(String fieldTrialsInitString);

6.2.4 PeerConnectionFactory其他函数

//创建Factory

private static native long nativeCreatePeerConnectionFactory();

//创建指令回调接口(与ICE服务器进行交互的指令)

private static native long nativeCreateObserver(PeerConnection.Observer observer);

//创建PeerConnection

private static native long nativeCreatePeerConnection(long nativeFactory, PeerConnection.RTCConfiguration rtcConfig, ediaConstraints constraints, long nativeObserver);

//创建本地音视频流

private static native long nativeCreateLocalMediaStream(long nativeFactory, String label);

//创建本地视频源

private static native long nativeCreateVideoSource(long nativeFactory,long nativeVideoCapturer,  MediaConstraints constraints);

//创建视频轨

private static native long nativeCreateVideoTrack(long nativeFactory, String id, long nativeVideoSource);

//创建本地音频流

private static native long nativeCreateAudioSource(long nativeFactory, MediaConstraints constraints);

//创建音频轨

private static native long nativeCreateAudioTrack(long nativeFactory, String id, long nativeSource);

//设置相关网络参数

public native void nativeSetOptions(long nativeFactory, Options options);

//设置视频硬件加速参数

private static native void nativeSetVideoHwAccelerationOptions(long nativeFactory, Object renderEGLContext);

//回收PeerConnectionFactory

private static native void freeFactory(long nativeFactory);

6.3 PeerConnection相关Native函数

6.3.1相关信令状态

//检测本地candidate的状态:刚刚创建、正在收集、完成收集

** Tracks PeerConnectionInterface::IceGatheringState */

public enum IceGatheringState { NEWGATHERINGCOMPLETE };

//检测远端candidate的状态

/** Tracks PeerConnectionInterface::IceConnectionState */

public enum IceConnectionState {

NEWCHECKINGCONNECTEDCOMPLETEDFAILEDDISCONNECTEDCLOSED

};

//检测与Sigal信令服务器连接的状态

/** Tracks PeerConnectionInterface::SignalingState */

public enum SignalingState {

STABLEHAVE_LOCAL_OFFERHAVE_LOCAL_PRANSWERHAVE_REMOTE_OFFER,

HAVE_REMOTE_PRANSWERCLOSED

};

6.3.2 Native函数介绍

//得到本地sdp描述

public native SessionDescription getLocalDescription();

//得到远端sdp描述

public native SessionDescription getRemoteDescription();

//创建数据通道

public native DataChannel createDataChannel(String label, DataChannel.Init init);

//创建offer消息

public native void createOffer(SdpObserver observer, MediaConstraints constraints);

//创建answer消息

public native void createAnswer(SdpObserver observer, MediaConstraints constraints);

//设置本地sdp

public native void setLocalDescription(SdpObserver observer, SessionDescription sdp);

//设置远端sdp

public native void setRemoteDescription(SdpObserver observer, SessionDescription sdp);

//更新IceServer

public native boolean updateIce(List<IceServer> iceServers, MediaConstraints constraints);

//得到信令状态

public native SignalingState signalingState();

//获得远端连接状态

public native IceConnectionState iceConnectionState();

//获得本地连接状态

public native IceGatheringState iceGatheringState();

//关闭与Ice服务器的连接

public native void close();

//释放PeerConnection

private static native void freePeerConnection(long nativePeerConnection);

//释放Observer

private static native void freeObserver(long nativeObserver);

//添加新的Candidate

private native boolean nativeAddIceCandidate(String sdpMid, int sdpMLineIndex, String iceCandidateSdp);

//添加本地流

private native boolean nativeAddLocalStream(long nativeStream);

//移除本地流

private native void nativeRemoveLocalStream(long nativeStream);

//得到StatsObserver的状态

private native boolean nativeGetStats(StatsObserver observer, long nativeTrack);

八、Native函数之音视频

一旦有了peerConnectionFactory实例,就应该从你的设备上获取音频和视频了,最终渲染到屏幕上。VideoCapturerAndroid,VideoSource,VideoTrack和VideoRenderer,都是以VideoCapturerAndroid开始。

8.1 VideoCapturerAndroid

VideoCapturerAndroid类是一个相机的包装类,提供访问设备相机数据流的江边方法。允许你获取设备数量,获取前置后置摄像头

// Returns the number of camera devices

VideoCapturerAndroid.getDeviceCount();

// Returns the front face device name

VideoCapturerAndroid.getNameOfFrontFacingDevice();

// Returns the back facing device name

VideoCapturerAndroid.getNameOfBackFacingDevice();

// Creates a VideoCapturerAndroid instance for the device name

VideoCapturerAndroid.create(name);

使用VideoCapturerAndroid类的实例,可以创建包含相机视频流的MediaStream,你可以给对方发送数据。

8.2 VideoSource/VideoTrack

VideoSource可以开始或停止你的设备。在无用停止抓取信息有助于电池使用寿命的延长。

VideoTrack是一个添加VideoSource到MediaStream对象的一个包装。

8.3 AudioSource/AudioTrack

除了不需要AudioCapturer获取麦克风数据,AudioSource/AudioTrack和VideoSource/VideoTrack很类似。audioConstraints是MediaContraints的实例。

8.4 VideoRenderer

VideoRendererGui是一个GLSurfaceView,在这之上,可以显示视频流,增加我们的renderer到VideoTrack上。

// To create our VideoRenderer, we can use the// included VideoRendererGui for simplicity// First we need to set the GLSurfaceView that it should render to

GLSurfaceView videoView = (GLSurfaceView) findViewById(R.id.glview_call);

// Then we set that view, and pass a Runnable// to run once the surface is ready

VideoRendererGui.setView(videoView, runnable);

// Now that VideoRendererGui is ready, we can get our VideoRenderer

VideoRenderer renderer = VideoRendererGui.createGui(x, y, width, height);

// And finally, with our VideoRenderer ready, we// can add our renderer to the VideoTrack.

localVideoTrack.addRenderer(renderer);

8.5 MediaConstraints

这个MediaConstraints是WebRTC支持将视频和音频放入MediaStream的方式。看这个支持的规范,大多数方法都需要MediaContraints的实例。

8.6 MediaStream

getUserMedia直接返回一个MediaStream,可以直接将其添加到RTCPeerConnection中发送给对端。

// We start out with an empty MediaStream object,

// created with help from our PeerConnectionFactory

// Note that LOCAL_MEDIA_STREAM_ID can be any string

MediaStream mediaStream = peerConnectionFactory.createLocalMediaStream(LOCAL_MEDIA_STREAM_ID);

// Now we can add our tracks.

mediaStream.addTrack(localVideoTrack);

mediaStream.addTrack(localAudioTrack);

WebRTC之Android客户端的更多相关文章

  1. Android客户端和服务器端数据交互

    网上有很多例子来演示Android客户端和服务器端数据如何实现交互不过这些例子大多比较繁杂,对于初学者来说这是不利的,现在介绍几种代码简单.逻辑清晰的交互例子,本篇博客介绍第四种: 一.服务器端: 代 ...

  2. appium 自动化测试之知乎Android客户端

    appium是一个开源框架,相对来说还不算很稳定.转载请注明出处!!!! 前些日子,配置好了appium测试环境,至于环境怎么搭建,参考:http://www.cnblogs.com/tobecraz ...

  3. 仿优酷Android客户端图片左右滑动(自动滑动)

    最终效果: 页面布局main.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayou ...

  4. 【原创】轻量级即时通讯技术MobileIMSDK:Android客户端开发指南

    申明:MobileIMSDK 目前为个人维护的原创开源工程,现陆续整理了一些资料,希望对需要的人有用.如需与作者交流,见文章底签名处,互相学习. MobileIMSDK开源工程的代码托管地址请进入 G ...

  5. 基于SuperSocket的IIS主动推送消息给android客户端

    在上一篇文章<基于mina框架的GPS设备与服务器之间的交互>中,提到之前一直使用superwebsocket框架做为IIS和APP通信的媒介,经常出现无法通信的问题,必须一天几次的手动回 ...

  6. Android客户端性能优化(魅族资深工程师毫无保留奉献)

    本文由魅族科技有限公司资深Android开发工程师degao(嵌入式企鹅圈原创团队成员)撰写,是degao在嵌入式企鹅圈发表的第一篇原创文章,毫无保留地总结分享其在领导魅族多个项目开发中的Androi ...

  7. 微信Android客户端架构演进之路

    这是一个典型的Android应用在从小到大的成长过程中的“踩坑”与“填坑”的历史.互联网的变化速度如此之快,1年的时间里,可以发生翻天覆地的变化.今天在这里,重新和大家回顾微信客户端架构的演进过程,以 ...

  8. Android 客户端设计之解决方案

    解决方案,是正对与需求来谈的.一个抽象的需求,需要一个较为上层抽象的解决方案来处理,这是病和药的关系.但是一个解决方案,可能会包含多个功能,每个功能都是解决方案上的一个节点.一个优秀的解决方案必然需要 ...

  9. Android 客户端设计之环境考虑

    我做过两三个android客户端应用的整体设计和部分的编码,这里仅仅谈一下设计方面的故事(此乃原创2015:11:02). 做客户端设计,首先要考虑应用所在的环境,包括三方面:1 要设计的apk是在一 ...

随机推荐

  1. jmeter的使用---控制器

    1.如果(If)控制器.Switch Controller if控制语句,判断字段是否存在,或者符合,执行不同的逻辑 2.简单控制器 一次进件流程,需要不同模块的数据,例如登陆,提交个人信息,信用认证 ...

  2. 莫愁前路无知己,天下谁人不识Redis

    1. 数据库小知识 1.1 什么是数据库 数据库是"按照数据结构来组织.存储和管理数据的仓库".是一个长期存储在计算机内的.有组织的.有共享的.统一管理的数据集合.数据库是以一定方 ...

  3. Bugku-CTF分析篇-日志审计(请从流量当中分析出flag)

    日志审计 请从流量当中分析出flag

  4. AcWing 240. 食物链

    #include <iostream> using namespace std; ; int n, m; int p[N], d[N]; //p是baba,d是距离 int find(in ...

  5. python evel()的用法

    老生常谈部分: eval(expression[, globals[, locals]]) expression -- 表达式. globals -- 变量作用域,全局命名空间,如果被提供,则必须是一 ...

  6. JS json对象(Object)和字符串(String)互转方法

    [JS json对象(Object)和字符串(String)互转方法] 参考:https://blog.csdn.net/wenqianla2550/article/details/78232706 ...

  7. ubuntu12.04安装Opencv2.4.9

    之前在Linux下装过几次opencv,但几乎每次都要查一下怎么安装,这次索性记录一下安装过程,不用每次都看其他人的教程了. 至于安装过程,可以直接参考官方文档.在解压后的文件夹下opencv\bui ...

  8. Bug搬运工-Forerunner CRC error on 54SG/53SG3 triggers watchdog timeout crash

    这个bug,一般是设备4948 Crash的情况: 标志1:Error Message C4K_SUPERVISOR-2-SOFTERROR: memory inconsistency detecte ...

  9. JavaScript复习总结一(入门)

    总是执着想学各种框架,但忘了基础学好才最重要.每次打开菜鸟教程想重温基础内容,然后就像翻开英文字典,永远在abandon...还是需要做个笔记. 一来加深学习印象,二来等下次打开学习可以知道自己上次学 ...

  10. 关于C++指针、引用和const关键字的各种关系

    #include <stdio.h> #include<iostream> using namespace std; typedef char *new_type; int m ...