一.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. IntelliJ IDEA 2017.3尚硅谷-----滚轮修改字体大小

  2. tmux的基本使用

    tmux的基本使用 tmux 最近发现了一个linux终端非常好用的工具,可以快速分屏 使用方法 参考

  3. Mysq的安装

    1.安装包下载 2.安装教程 (1)配置环境变量 (2)生成data文件 (3)安装MySQL (4)启动服务 (5)登录MySQL (6)查询用户密码 (7)设置修改用户密码 (8)退出 1.安装包 ...

  4. Go_Json序列化

    1. json介绍 2. json格式说明 3. json序列化 3.1 结构体序列化 package main import ( "fmt" "encoding/jso ...

  5. requests使用小结(不定期更新)

    request是python的第三方库,使用上比urllib和urllib2要更方便. 0x01 使用session发送:能保存一条流中获取的cookie,并自动添加到http头中 s = reque ...

  6. mybatis用mysql数据库自增主键,插入一条记录返回新增记录的自增主键ID

    今天在敲代码的时候遇到一个问题,就是往数据库里插入一条记录后需要返回这个新增记录的ID(自增主键), 公司框架用的是mybatis的通用Mapper接口,里面的插入方法貌似是不能把新纪录的ID回填到对 ...

  7. 远程连接本地mongodb 数据库

    绑定本地IP即可 start mongod --dbpath D:\mongodb\data\db --bind_ip 192.168.31.143

  8. 刷题5. Longest Palindromic Substring

    一.题目说明 Longest Palindromic Substring,求字符串中的最长的回文. Difficuty是Medium 二.我的实现 经过前面4个题目,我对边界考虑越来越"完善 ...

  9. scala安装此时不应有 \scala\bin\scala.bat

    scala安装目录有空格导致的,不应该有空格

  10. 吴裕雄 python 神经网络——TensorFlow图片预处理调整图片

    import numpy as np import tensorflow as tf import matplotlib.pyplot as plt def distort_color(image, ...