概述
上节完成webrtc音视频通信以及sdp、ice详细讲解。
- sdp描述多媒体通信会话的各项参数,如视频参数、音频参数
- ice是用于点对点连接的网络信息
webrtc正式通信之前,首先交互sdp,再交互ice。交互完成后开始创建通道建立。webrtc建立通道可以分成三类: - 视频通道
- 音频通道
- 数据通道 webrtc通信,是点对点通信。嘟宝与嘟妈是直接相互连接,而不通过服务器,这是去中心化。而如今我们的网络设备都处在NAT,是无法直接相互连接的。这就需要一个工具,coturn打通一个通道。而打通通道在不同网络中会有失败的可能。所有连接的通道合在一个,这个通道同时传输视频通道、音频通道、数据通道。

数据通道
WebRTC 数据通道(Data Channel)是一个强大的功能,允许你在两个 PeerConnection 之间直接传输任意数据,无需经过服务器中转(理论上),延迟极低,非常适合实时游戏、文件传输、聊天等场景。 
数据通道的创建
- 创建RTCPeerConnection对象pc
- 创建数据通道createDataChannel对象
- 创建数据通道再创建offer交互sdp
嘟妈创建数据通道
clike
const configuration = {
iceServers: [
{ urls: 'stun:192.168.1.20:3478' },
]
}
peerConnection = new RTCPeerConnection(configuration)
// ✅ 创建数据通道(必须在创建 Offer 之前!)
dataChannel = peerConnection.createDataChannel('chat', {
ordered: true, // 保证顺序
maxRetransmits: 3, // 最大重传次数(不设置则可靠传输)
// 或者使用 maxPacketLifeTime: 1000 // 最大存活时间(毫秒)
});
// 监听数据通道事件
dataChannel.onopen = () => {
console.log(' 数据通道已打开');
dataChannel.send('Hello from offerer!');
};
dataChannel.onmessage = (event:any) => {
console.log('收到消息:', event.data);
};
dataChannel.onclose = () => {
console.log('数据通道已关闭');
};
在交互完sdp与iec建立连接后,数据通道正式建立,回调onopen 。
发送二进制数据
clike
// 发送 ArrayBuffer
const buffer = new ArrayBuffer(1024);
dataChannel.send(buffer);
// 发送 Blob
const blob = new Blob(['hello'], { type: 'text/plain' });
dataChannel.send(blob);
// 发送 TypedArray
const int16Array = new Int16Array([1, 2, 3, 4]);
dataChannel.send(int16Array.buffer);
注意事项
- 必须在创建 Offer 前创建数据通道(作为发起方)
- 数据大小限制:单个消息通常限制在 16KB-256KB,大文件需要分块
- 信令交换:数据通道的协商随 SDP 一起完成,不需要额外信令
- ICE 重启:数据通道会在 ICE 重启后自动恢复
嘟宝创建数据通道
clike
List<PeerConnection.IceServer> iceServers=new ArrayList <>();
iceServers.add(PeerConnection.IceServer.builder("stun:192.168.1.20:3478").createIceServer());
PeerConnection.RTCConfiguration config =new PeerConnection.RTCConfiguration(iceServers);
peerConnection=factory.createPeerConnection(config, new PeerConnection.Observer() {
@Override
public void onDataChannel(DataChannel dataChannel) {
mdataChannel=dataChannel;
setupDataChannelObserver();
print("onDataChannel--------------------------");
}
@Override
public void onRenegotiationNeeded() {
}
@Override
public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
print("onAddTrack!!!!!!!!!!!!!!!!!!!!");
}
});
Android sdp在创建PeerConnection时,通过回调自动创建数据通道DataChannel 。 在onDataChannel函数中获取dataChannel对象,调用setupDataChannelObserver创建dataChannel事件信息
clike
if (mdataChannel == null) {
return;
}
mdataChannel.registerObserver(new DataChannel.Observer() {
@Override
public void onBufferedAmountChange(long l) {
print("webrtc缓冲区变化:"+l);
}
@Override
public void onStateChange() {
DataChannel.State state = mdataChannel.state();
Log.d("WebRTC", ": " + state);
print("WebRTC 数据通道状态:"+state);
if (state == DataChannel.State.OPEN) {
Log.d("mqtt", "数据通道已打开");
// 可以发送初始消息
} else if (state == DataChannel.State.CLOSED) {
Log.d("mqtt", " 数据通道已关闭");
}
}
@Override
public void onMessage(DataChannel.Buffer buffer) {
if (buffer.binary) {
// 二进制数据
ByteBuffer byteBuffer = buffer.data;
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
} else {
// 文本消息
ByteBuffer byteBuffer = buffer.data;
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
String message = new String(bytes);
sendMessage("hello !!!!!!!");
}
}
});
}
通过sendMessage发送数据
clike
// 发送文本消息
public void sendMessage(String message) {
if (mdataChannel != null && mdataChannel.state() == DataChannel.State.OPEN) {
byte[] bytes = message.getBytes();
ByteBuffer buffer = ByteBuffer.wrap(bytes);
DataChannel.Buffer dataBuffer = new DataChannel.Buffer(buffer, false);
boolean success = mdataChannel.send(dataBuffer);
Log.d("WebRTC", "发送消息: " + message + ", 结果: " + success);
} else {
Log.e("WebRTC", "数据通道未打开");
}
}
发送二进制数据
clike
// 发送二进制数据
public void sendBinaryData(byte[] data) {
if (mdataChannel != null && mdataChannel.state() == DataChannel.State.OPEN) {
ByteBuffer buffer = ByteBuffer.wrap(data);
DataChannel.Buffer dataBuffer = new DataChannel.Buffer(buffer, true);
boolean success = mdataChannel.send(dataBuffer);
Log.d("WebRTC", "发送二进制数据长度: " + data.length + ", 结果: " + success);
}
}
嘟宝完成类设计
clike
package com.zilong.dubao;
import android.content.Context;
import android.content.Intent;
import android.media.projection.MediaProjection;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import org.json.JSONObject;
import org.webrtc.AudioSource;
import org.webrtc.AudioTrack;
import org.webrtc.Camera1Enumerator;
import org.webrtc.Camera2Enumerator;
import org.webrtc.CameraVideoCapturer;
import org.webrtc.DataChannel;
import org.webrtc.DefaultVideoDecoderFactory;
import org.webrtc.DefaultVideoEncoderFactory;
import org.webrtc.EglBase;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.RtpReceiver;
import org.webrtc.RtpSender;
import org.webrtc.ScreenCapturerAndroid;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
import org.webrtc.SurfaceTextureHelper;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
public class MyWebRtc {
private EglBase eglBase;
private PeerConnectionFactory factory;
PeerConnection peerConnection;
Context context;
DataChannel mdataChannel;
MyWebRtc(){
this.context=app.getContext();
init();
}
private void print(String s){
Log.d("mqtt",s);
}
private void init() {
eglBase = EglBase.create();
// 初始化 WebRTC
PeerConnectionFactory.InitializationOptions options =
PeerConnectionFactory.InitializationOptions
.builder(context)
.createInitializationOptions();
PeerConnectionFactory.initialize(options);
factory = PeerConnectionFactory.builder()
.setVideoEncoderFactory(new DefaultVideoEncoderFactory(eglBase.getEglBaseContext(),true,true))
.setVideoDecoderFactory(new DefaultVideoDecoderFactory(eglBase.getEglBaseContext()))
.createPeerConnectionFactory();
}
public void createPeerConnection(String remoteSdp) {
List<PeerConnection.IceServer> iceServers=new ArrayList <>();
iceServers.add(PeerConnection.IceServer.builder("stun:192.168.1.20:3478").createIceServer());
PeerConnection.RTCConfiguration config =new PeerConnection.RTCConfiguration(iceServers);
peerConnection=factory.createPeerConnection(config, new PeerConnection.Observer() {
@Override
public void onSignalingChange(PeerConnection.SignalingState signalingState) {
}
@Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
print("onIceConnectionChange"+iceConnectionState.name());
if(iceConnectionState==PeerConnection.IceConnectionState.FAILED){
}
}
@Override
public void onIceConnectionReceivingChange(boolean b) {
}
@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
}
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
js_IceCandidate js=new js_IceCandidate();
js.candidate=iceCandidate.sdp;
js.sdpMid=iceCandidate.sdpMid;
js.sdpMLineIndex=iceCandidate.sdpMLineIndex;
Gson gson = new Gson();
String s = gson.toJson(js);
pushmsg("ice",s);
}
@Override
public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
}
@Override
public void onAddStream(MediaStream mediaStream) {
}
@Override
public void onRemoveStream(MediaStream mediaStream) {
}
@Override
public void onDataChannel(DataChannel dataChannel) {
mdataChannel=dataChannel;
setupDataChannelObserver();
print("onDataChannel--------------------------");
}
@Override
public void onRenegotiationNeeded() {
}
@Override
public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
print("onAddTrack!!!!!!!!!!!!!!!!!!!!");
}
});
peerConnection.addTrack(getAudioTrack());
localVideoSender =peerConnection.addTrack(getVideoTrack());
SessionDescription offer=JSONSessionDescription(remoteSdp);
peerConnection.setRemoteDescription(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
print("onCreateSuccess");
}
@Override
public void onSetSuccess() {
print("onSetSuccess");
createAnswer();
}
@Override
public void onCreateFailure(String s) {
print(s);
}
@Override
public void onSetFailure(String s) {
print(s);
}
}, offer);
}
private AudioTrack getAudioTrack(){
AudioSource audioSource =
factory.createAudioSource(
new MediaConstraints());
AudioTrack audioTrack =
factory.createAudioTrack(
"audio_track",
audioSource);
audioTrack.enabled();
return audioTrack;
}
private CameraVideoCapturer mCamCapture;
private SurfaceTextureHelper surfaceTextureHelper;
private SurfaceTextureHelper surfaceTextureHelperscreen;
private RtpSender localVideoSender;
static Intent i;
private VideoTrack getVideoTrack(){
surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", eglBase.getEglBaseContext());
VideoCapturer videoCapturer = createCameraCapturer(true);
VideoSource videoSource = factory.createVideoSource(videoCapturer.isScreencast());
videoCapturer.initialize(surfaceTextureHelper, context.getApplicationContext(), videoSource.getCapturerObserver());
videoCapturer.startCapture(480, 640, 30);
VideoTrack videoTrack= factory.createVideoTrack("100", videoSource);
videoTrack.enabled();
return videoTrack;
}
private void setupDataChannelObserver() {
if (mdataChannel == null) {
return;
}
mdataChannel.registerObserver(new DataChannel.Observer() {
@Override
public void onBufferedAmountChange(long l) {
print("webrtc缓冲区变化:"+l);
}
@Override
public void onStateChange() {
DataChannel.State state = mdataChannel.state();
Log.d("WebRTC", ": " + state);
print("WebRTC 数据通道状态:"+state);
if (state == DataChannel.State.OPEN) {
Log.d("mqtt", "✅ 数据通道已打开");
// 可以发送初始消息
} else if (state == DataChannel.State.CLOSED) {
Log.d("mqtt", "❌ 数据通道已关闭");
}
}
@Override
public void onMessage(DataChannel.Buffer buffer) {
if (buffer.binary) {
// 二进制数据
ByteBuffer byteBuffer = buffer.data;
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
// handleBinaryMessage(bytes);
} else {
// 文本消息
ByteBuffer byteBuffer = buffer.data;
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
String message = new String(bytes);
// handleTextMessage(message);
print(message);
sendMessage("shoudaoshsdasdasd");
}
}
});
}
// 发送文本消息
public void sendMessage(String message) {
if (mdataChannel != null && mdataChannel.state() == DataChannel.State.OPEN) {
byte[] bytes = message.getBytes();
ByteBuffer buffer = ByteBuffer.wrap(bytes);
DataChannel.Buffer dataBuffer = new DataChannel.Buffer(buffer, false);
boolean success = mdataChannel.send(dataBuffer);
Log.d("WebRTC", "发送消息: " + message + ", 结果: " + success);
} else {
Log.e("WebRTC", "数据通道未打开");
}
}
// 发送二进制数据
public void sendBinaryData(byte[] data) {
if (mdataChannel != null && mdataChannel.state() == DataChannel.State.OPEN) {
ByteBuffer buffer = ByteBuffer.wrap(data);
DataChannel.Buffer dataBuffer = new DataChannel.Buffer(buffer, true);
boolean success = mdataChannel.send(dataBuffer);
Log.d("WebRTC", "发送二进制数据长度: " + data.length + ", 结果: " + success);
}
}
public void getScreenVideo() {
surfaceTextureHelperscreen = SurfaceTextureHelper.create("CaptureThread1", eglBase.getEglBaseContext());
VideoCapturer screenCapturer=new ScreenCapturerAndroid(i, new MediaProjection.Callback() {
@Override
public void onStop() {
super.onStop();
}
}); // eglBaseContext 是你之前初始化的 EGL 上下文
VideoSource screenVideoSource = factory.createVideoSource(screenCapturer.isScreencast());
VideoTrack screenVideoTrack = factory.createVideoTrack("102", screenVideoSource);
// 2. 初始化并启动 ScreenCapturer
// 注意:需要提前准备好 SurfaceTextureHelper
screenCapturer.initialize(surfaceTextureHelperscreen, context.getApplicationContext(), screenVideoSource.getCapturerObserver());
screenCapturer.startCapture(/* width */ 640, /* height */ 480, /* fps */ 25);
// 3. 执行替换的关键操作
// localVideoSender.track().setEnabled(false);
localVideoSender.setTrack(screenVideoTrack, /* takeOwnership= */ true);
}
public void changeCam(){
if (mCamCapture != null) {
mCamCapture.switchCamera(null);
}
}
private VideoCapturer createCameraCapturer(boolean isFront) {
Camera2Enumerator enumerator = new Camera2Enumerator(context.getApplicationContext());
final String[] deviceNames = enumerator.getDeviceNames();
for (String deviceName : deviceNames) {
if (isFront ? enumerator.isFrontFacing(deviceName) : enumerator.isBackFacing(deviceName)) {
VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);
mCamCapture = (CameraVideoCapturer) videoCapturer;
if (videoCapturer != null) {
return videoCapturer;
}
}
}
return null;
}
private void createAnswer(){
peerConnection.createAnswer(new SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
peerConnection.setLocalDescription(this,sessionDescription);
pushmsg("answer",(SessionDescriptionJSON(sessionDescription)));
}
@Override
public void onSetSuccess() {
print("answer onSetSuccess");
}
@Override
public void onCreateFailure(String s) {
}
@Override
public void onSetFailure(String s) {
}
}, new MediaConstraints());
}
public String SessionDescriptionJSON(SessionDescription description){
try {
JS_SessionDescription s=new JS_SessionDescription();
switch (description.type){
case ANSWER:
s.type="answer";
break;
default:break;
}
s.sdp=description.description;
Gson gson = new Gson();
return gson.toJson(s);
} catch (JsonSyntaxException e) {
e.printStackTrace();
return "";
}
}
public void onRemoteIceCandidateReceived(String s) {
// print("发起者收到 onRemoteIceCandidateReceived10"+s);
peerConnection.addIceCandidate(jsonToIceCandidate(s));
}
class JS_SessionDescription{
String sdp="";
String type="";
}
class js_IceCandidate{
public String sdpMid;
public int sdpMLineIndex;
public String candidate;
public String usernameFragment="";
}
public IceCandidate jsonToIceCandidate(String s) {
Gson gson = new Gson();
js_IceCandidate c = gson.fromJson(s, js_IceCandidate.class);
return new IceCandidate(c.sdpMid, c.sdpMLineIndex, c.candidate);
}
public SessionDescription JSONSessionDescription(String s){
try {
Gson gson = new Gson();
JS_SessionDescription offer = gson.fromJson(s, JS_SessionDescription.class);
SessionDescription.Type type= SessionDescription.Type.OFFER;
switch (offer.type) {
case "offer":
type = SessionDescription.Type.OFFER;
break;
case "answer":
type = SessionDescription.Type.ANSWER;
break;
case "pranswer":
type = SessionDescription.Type.PRANSWER;
break;
}
return new SessionDescription(type, offer.sdp);
} catch (JsonSyntaxException e) {
e.printStackTrace();
return null;
}
}
private void pushmsg(String code,String data){
MyMqttClient.Msg msg=new MyMqttClient.Msg();
msg.dubaoId=MyService.dubaoId;
msg.dumaId="f1122aeb-f2b0-400d-9919-eddd2eaebaa2";
msg.dumaName="天使嘟妈";
msg.code=code;
msg.data=data;
Gson gson=new Gson();
String json=gson.toJson(msg);
MyService.myMqttClient.publish("/duma/"+ msg.dumaId,json);
}
}