(factoryPointer),
+// (webrtc::PeerConnectionFactoryInterface*) factoryPointer,
+ rtcConfiguration
+ );
+// env->ReleaseStringUTFChars(rtpCapabilities, routerRtpCapabilities);
+// delete routerRtpCapabilities;
+ }
+
+ extern "C" JNIEXPORT void JNICALL Java_com_acgist_taoyao_media_Room_nativeNewClient(JNIEnv * env, jobject me) {
+
+ }
+
+ extern "C" JNIEXPORT void JNICALL Java_com_acgist_taoyao_media_Room_nativeCloseClient(JNIEnv * env, jobject me) {
+ }
+
+ extern "C" JNIEXPORT void JNICALL Java_com_acgist_taoyao_media_Room_nativeCloseRoom(JNIEnv * env, jobject me, jlong nativePointer) {
+// JNIEXPORT void JNICALL
+// Java_nativeMethod
+// (JNIEnv *env, jobject thiz) {
+// MyCPlusObj *obj = new MyCPlusObj();
+// jclass clazz = (jclass)(*env).GetObjectClass(thiz);
+// jfieldID fid = (jfieldID)(*env).GetFieldID(clazz, "mObj", "I");
+// (*env).SetIntField(thiz, fid, (jint)obj);
+// }
+
+// jclass objClazz = (jclass)env->GetObjectClass(obj);//obj为对应的JAVA对象
+// jfieldID fid = env->GetFieldID(objClazz, "mObj", "I");
+// jlong p = (jlong)env->GetObjectField(obj, fid);
+// MyCPlusObj *cPlusObj = (MyCPlusObj *)p;
+////cPlusObj 为JAVA对象对应的C++对象
+
+// jobject gThiz = (jobject)env->NewGlobalRef(thiz);//thiz为JAVA对象
+// (*obj).javaObj = (jint)gThiz;
+
+ Room* room = (Room*) nativePointer;
+ room->close();
+ delete room;
+ }
}
-
-void init() {
- std::cout << "加载MediasoupClient:" << mediasoupclient::Version() << std::endl;
- std::cout << "加载libwebrtc" << std::endl;
- mediasoupclient::Initialize();
-// mediasoupclient::parseScalabilityMode("L2T3");
- // => { spatialLayers: 2, temporalLayers: 3 }
-// mediasoupclient::parseScalabilityMode("L4T7_KEY_SHIFT");
- // => { spatialLayers: 4, temporalLayers: 7 }
-}
-
-void load() {
- // TODO:JNI信令交互
-// if(acgist::Room::pDevice == nullptr) {
-// acgist::Room::pDevice = new mediasoupclient::Device();
-// acgist::Room::pDevice->Load();
-// }
-}
-
-void stop() {
- std::cout << "释放libwebrtc" << std::endl;
- mediasoupclient::Cleanup();
-}
\ No newline at end of file
diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/RouterCallback.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/RouterCallback.java
new file mode 100644
index 0000000..bb94691
--- /dev/null
+++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/RouterCallback.java
@@ -0,0 +1,16 @@
+package com.acgist.taoyao;
+
+/**
+ * 路由回调
+ */
+public interface RouterCallback {
+
+ void enterCallback();
+ void newRemoteClientCallback();
+ void closeRemoteClientCallback();
+ void consumerPauseCallback();
+ void consumerResumeCallback();
+ void producerPauseCallback();
+ void producerResumeCallback();
+
+}
diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/LocalClient.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/LocalClient.java
index 17410f7..f90e3df 100644
--- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/LocalClient.java
+++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/LocalClient.java
@@ -16,7 +16,13 @@ public class LocalClient extends RoomClient {
*/
public enum TransportType {
+ /**
+ * RTP
+ */
RTP,
+ /**
+ * WebRTC
+ */
WEBRTC;
}
diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaManager.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaManager.java
index 6cb2400..cff1568 100644
--- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaManager.java
+++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaManager.java
@@ -2,6 +2,8 @@ package com.acgist.taoyao.media;
import android.content.Context;
import android.content.Intent;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
import android.media.projection.MediaProjection;
import android.os.Handler;
import android.os.Looper;
@@ -28,8 +30,6 @@ import org.webrtc.SurfaceViewRenderer;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoDecoderFactory;
import org.webrtc.VideoEncoderFactory;
-import org.webrtc.VideoFrame;
-import org.webrtc.VideoSink;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;
import org.webrtc.audio.JavaAudioDeviceModule;
@@ -37,12 +37,14 @@ import org.webrtc.audio.JavaAudioDeviceModule;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
/**
* 媒体来源管理器
*
* @author acgist
- *
+ *
* https://zhuanlan.zhihu.com/p/82446482
* https://www.jianshu.com/p/97acd9a51909
* https://juejin.cn/post/7036308428305727519
@@ -53,29 +55,37 @@ import java.util.List;
* https://blog.csdn.net/u011418943/article/details/127108642
* https://blog.csdn.net/csdn_shen0221/article/details/120331004
* https://blog.csdn.net/csdn_shen0221/article/details/119982257
- *
+ *
* TODO:动态码率(BITRATE_MODE_VBR、BITRATE_MODE)
*/
public class MediaManager {
/**
- * 来源类型
+ * 视频来源类型
*
* @author acgist
*/
public enum Type {
- // 文件共享
+ /**
+ * 文件共享:FileVideoCapturer
+ */
FILE,
- // 后置摄像头
+ /**
+ * 后置摄像头:CameraVideoCapturer
+ */
BACK,
- // 前置摄像头
+ /**
+ * 前置摄像头:CameraVideoCapturer
+ */
FRONT,
- // 屏幕共享
+ /**
+ * 屏幕共享:ScreenCapturerAndroid
+ */
SCREEN;
/**
- * @return 是否摄像头
+ * @return 是否是摄像头
*/
public boolean isCamera() {
return this == BACK || this == FRONT;
@@ -89,9 +99,12 @@ public class MediaManager {
return INSTANCE;
}
+ /**
+ * 当前终端数量
+ */
private volatile int clientCount;
/**
- * 视频类型
+ * 视频来源类型
*/
private Type type;
/**
@@ -103,31 +116,28 @@ public class MediaManager {
*/
private Context context;
/**
- * 本地终端
- */
- private LocalClient localClient;
- /**
- *
+ * EGL
*/
private EglBase eglBase;
/**
- * 媒体流:声音、主码流(预览流)、次码流
+ * 媒体流:声音、视频
*/
private MediaStream mediaStream;
/**
* 视频捕获
- * FileVideoCapturer
- * CameraVideoCapturer
- * ScreenCapturerAndroid
*/
private VideoCapturer videoCapturer;
/**
- * Peer连接工厂
+ * PeerConnectionFactory
*/
private PeerConnectionFactory peerConnectionFactory;
+ /**
+ * 媒体录制
+ */
+ private final MediaRecorder mediaRecorder;
static {
- // 设置采样
+ // 采样
// WebRtcAudioUtils.setDefaultSampleRateHz();
// 噪声消除
// WebRtcAudioUtils.setWebRtcBasedNoiseSuppressor(true);
@@ -135,25 +145,59 @@ public class MediaManager {
// WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true);
// 自动增益
// WebRtcAudioUtils.setWebRtcBasedAutomaticGainControl(true);
- // 使用OpenSL ES
+ // OpenSL ES
// WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true);
+ final MediaCodecList mediaCodecList = new MediaCodecList(-1);
+ for (MediaCodecInfo mediaCodecInfo : mediaCodecList.getCodecInfos()) {
+// if (mediaCodecInfo.isEncoder()) {
+ final String[] supportedTypes = mediaCodecInfo.getSupportedTypes();
+ Log.d(MediaRecorder.class.getSimpleName(), "编码器名称:" + mediaCodecInfo.getName());
+ Log.d(MediaRecorder.class.getSimpleName(), "编码器类型:" + String.join(" , ", supportedTypes));
+ for (String supportType : supportedTypes) {
+ final MediaCodecInfo.CodecCapabilities codecCapabilities = mediaCodecInfo.getCapabilitiesForType(supportType);
+ final int[] colorFormats = codecCapabilities.colorFormats;
+ Log.d(MediaRecorder.class.getSimpleName(), "编码器格式:" + codecCapabilities.getMimeType());
+// MediaCodecInfo.CodecCapabilities.COLOR_*
+ Log.d(MediaRecorder.class.getSimpleName(), "编码器支持格式:" + IntStream.of(colorFormats).boxed().map(String::valueOf).collect(Collectors.joining(" , ")));
+ }
+// }
+ }
}
private MediaManager() {
this.clientCount = 0;
+ this.mediaRecorder = MediaRecorder.getInstance();
+ }
+
+ /**
+ * 初始化上下文
+ *
+ * @param handler Handler
+ * @param context 上下文
+ */
+ public void initContext(Handler handler, Context context) {
+ this.handler = handler;
+ this.context = context;
+ PeerConnectionFactory.initialize(
+ PeerConnectionFactory.InitializationOptions.builder(this.context)
+// .setEnableInternalTracer(true)
+ .createInitializationOptions()
+ );
}
/**
* 新建终端
+ * 第一个终端进入时没有初始化时,初始化所有资源。
*
- * @param type 视频类型:第一个终端进入有效
+ * @param type 视频类型
*
- * @return PeerConnectionFactory
+ * @return PeerConnectionFactory PeerConnectionFactory
*/
public PeerConnectionFactory newClient(Type type) {
synchronized (this) {
- if(this.clientCount <= 0) {
+ if (this.clientCount <= 0 && !MediaRecorder.getInstance().isActive()) {
this.initMedia(type);
+ this.nativeInit();
}
this.clientCount++;
}
@@ -162,36 +206,48 @@ public class MediaManager {
/**
* 关闭一个终端
+ * 最后一个终端关闭时,释放所有资源。
*
* @return 剩余终端数量
*/
public int closeClient() {
synchronized (this) {
this.clientCount--;
- if(this.clientCount <= 0) {
+ if (this.clientCount <= 0) {
this.close();
+ this.nativeStop();
}
return this.clientCount;
}
}
- public void initMedia(Handler handler, Context context) {
- this.handler = handler;
- this.context = context;
+ public void initRecord(Type type) {
+ synchronized (this) {
+ if(this.clientCount <= 0 && !MediaRecorder.getInstance().isActive()) {
+ this.initMedia(type);
+ this.nativeInit();
+ }
+ }
+ }
+
+ public void stopRecord() {
+ synchronized (this) {
+ if(this.clientCount <= 0) {
+ this.close();
+ this.nativeStop();
+ }
+ }
}
/**
- * 加载媒体流
+ * 加载媒体
+ *
+ * @param type 视频来源类型
*/
- public void initMedia(Type type) {
+ private void initMedia(Type type) {
Log.i(MediaManager.class.getSimpleName(), "加载媒体:" + type);
this.type = type;
this.eglBase = EglBase.create();
- PeerConnectionFactory.initialize(
- PeerConnectionFactory.InitializationOptions.builder(this.context)
-// .setEnableInternalTracer(true)
- .createInitializationOptions()
- );
final VideoDecoderFactory videoDecoderFactory = new DefaultVideoDecoderFactory(this.eglBase.getEglBaseContext());
final VideoEncoderFactory videoEncoderFactory = new DefaultVideoEncoderFactory(this.eglBase.getEglBaseContext(), true, true);
final JavaAudioDeviceModule javaAudioDeviceModule = JavaAudioDeviceModule.builder(this.context)
@@ -223,22 +279,21 @@ public class MediaManager {
* 切换视频来源
*
* @param type 来源类型
- *
- * TODO:设置分享
*/
public void exchange(Type type) {
- if(this.type == type) {
+ if (this.type == type) {
return;
}
this.type = type;
Log.i(MediaManager.class.getSimpleName(), "设置视频来源:" + type);
- if(this.type.isCamera() && type.isCamera()) {
+ if (this.type.isCamera() && type.isCamera()) {
// TODO:测试是否需要完全重置
final CameraVideoCapturer cameraVideoCapturer = (CameraVideoCapturer) this.videoCapturer;
cameraVideoCapturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() {
@Override
public void onCameraSwitchDone(boolean success) {
}
+
@Override
public void onCameraSwitchError(String message) {
}
@@ -283,40 +338,44 @@ public class MediaManager {
*/
private void initVideo() {
this.closeVideoTrack();
- if(this.videoCapturer != null) {
+ if (this.videoCapturer != null) {
this.videoCapturer.dispose();
}
- if(this.type.isCamera()) {
- final CameraEnumerator cameraEnumerator = new Camera2Enumerator(this.context);
- final String[] names = cameraEnumerator.getDeviceNames();
- for(String name : names) {
- if(this.type == Type.FRONT && cameraEnumerator.isFrontFacing(name)) {
- Log.i(MediaManager.class.getSimpleName(), "加载视频(前置摄像头):" + name);
- this.videoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler());
- } else if(this.type == Type.BACK && cameraEnumerator.isBackFacing(name)) {
- Log.i(MediaManager.class.getSimpleName(), "加载视频(后置摄像头):" + name);
- this.videoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler());
- } else {
- Log.d(MediaManager.class.getSimpleName(), "忽略摄像头:" + name);
- }
- }
- this.initVideoTrack();
- } else if(this.type == Type.FILE) {
- Log.i(MediaManager.class.getSimpleName(), "加载视频(文件)");
- } else if(this.type == Type.SCREEN) {
- Log.i(MediaManager.class.getSimpleName(), "加载视频(录屏)");
+ Log.i(MediaManager.class.getSimpleName(), "加载视频:" + this.type);
+ if (this.type.isCamera()) {
+ this.initCamera();
+ } else if (this.type == Type.FILE) {
+ // 文件
+ } else if (this.type == Type.SCREEN) {
final Message message = new Message();
message.what = Config.WHAT_SCREEN_CAPTURE;
this.handler.sendMessage(message);
+ } else {
+ // 其他类型
}
}
+ private void initCamera() {
+ final CameraEnumerator cameraEnumerator = new Camera2Enumerator(this.context);
+ final String[] names = cameraEnumerator.getDeviceNames();
+ for (String name : names) {
+ if (this.type == Type.FRONT && cameraEnumerator.isFrontFacing(name)) {
+ this.videoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler());
+ } else if (this.type == Type.BACK && cameraEnumerator.isBackFacing(name)) {
+ this.videoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler());
+ } else {
+ // 忽略其他摄像头
+ }
+ }
+ this.initVideoTrack();
+ }
+
public void screenRecord(Intent intent) {
this.videoCapturer = new ScreenCapturerAndroid(intent, new MediaProjection.Callback() {
@Override
public void onStop() {
- super.onStop();
- Log.i(MediaManager.class.getSimpleName(), "停止屏幕捕获");
+ super.onStop();
+ Log.i(MediaManager.class.getSimpleName(), "停止屏幕捕获");
}
});
this.initVideoTrack();
@@ -326,7 +385,7 @@ public class MediaManager {
* 加载视频
*/
private void initVideoTrack() {
- final SurfaceViewRenderer surfaceViewRenderer = this.preview();
+ final SurfaceViewRenderer surfaceViewRenderer = this.localPreview();
// 加载视频
final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create("MediaVideoThread", this.eglBase.getEglBaseContext());
// surfaceTextureHelper.setTextureSize();
@@ -338,7 +397,7 @@ public class MediaManager {
this.videoCapturer.startCapture(480, 640, 30);
final VideoTrack videoTrack = this.peerConnectionFactory.createVideoTrack("ARDAMSv0", videoSource);
videoTrack.addSink(surfaceViewRenderer);
- videoTrack.addSink(MediaRecorder.getInstance().videoRecoder);
+// videoTrack.addSink(MediaRecorder.getInstance().videoRecoder);
videoTrack.setEnabled(true);
this.mediaStream.addTrack(videoTrack);
Log.i(MediaManager.class.getSimpleName(), "加载视频:" + videoTrack.id());
@@ -348,7 +407,7 @@ public class MediaManager {
return this.mediaStream;
}
- private SurfaceViewRenderer preview() {
+ private SurfaceViewRenderer localPreview() {
// 设置预览
final SurfaceViewRenderer surfaceViewRenderer = new SurfaceViewRenderer(this.context);
this.handler.post(() -> {
@@ -370,7 +429,7 @@ public class MediaManager {
message.obj = surfaceViewRenderer;
message.what = Config.WHAT_NEW_LOCAL_VIDEO;
// TODO:恢复
-// this.handler.sendMessage(message);
+ this.handler.sendMessage(message);
// 暂停
// surfaceViewRenderer.pauseVideo();
// 恢复
@@ -443,7 +502,7 @@ public class MediaManager {
synchronized (this.mediaStream.audioTracks) {
AudioTrack track;
final Iterator iterator = this.mediaStream.audioTracks.iterator();
- while(iterator.hasNext()) {
+ while (iterator.hasNext()) {
track = iterator.next();
iterator.remove();
track.dispose();
@@ -470,7 +529,7 @@ public class MediaManager {
synchronized (list) {
VideoTrack track;
final Iterator iterator = list.iterator();
- while(iterator.hasNext()) {
+ while (iterator.hasNext()) {
track = iterator.next();
iterator.remove();
track.dispose();
@@ -481,27 +540,32 @@ public class MediaManager {
/**
* 释放资源
*/
- public void close() {
+ private void close() {
this.closeAudioTrack();
this.closeVideoTrack();
- if(this.eglBase != null) {
+ if (this.eglBase != null) {
this.eglBase.release();
this.eglBase = null;
}
- if(this.videoCapturer != null) {
+ if (this.videoCapturer != null) {
this.videoCapturer.dispose();
this.videoCapturer = null;
}
- if(this.mediaStream != null) {
+ if (this.mediaStream != null) {
this.mediaStream.dispose();
this.mediaStream = null;
}
- if(this.peerConnectionFactory != null) {
+ if (this.peerConnectionFactory != null) {
this.peerConnectionFactory.dispose();
this.peerConnectionFactory = null;
}
}
+ public void shutdown() {
+// PeerConnectionFactory.shutdownInternalTracer();
+// PeerConnectionFactory.stopInternalTracingCapture();
+ }
+
/**
* 摄像头事件
*
@@ -534,4 +598,8 @@ public class MediaManager {
}
}
+ private native void nativeInit();
+ private native void nativeStop();
+ public native long nativeNewRoom(String roomId);
+
}
diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaRecorder.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaRecorder.java
index 7c2b2f9..95f3453 100644
--- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaRecorder.java
+++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/MediaRecorder.java
@@ -13,13 +13,22 @@ import android.util.Log;
import com.acgist.mediasoup.R;
+import org.webrtc.JavaI420Buffer;
+import org.webrtc.RtpSender;
+import org.webrtc.RtpTransceiver;
+import org.webrtc.TextureBufferImpl;
+import org.webrtc.ThreadUtils;
import org.webrtc.VideoFrame;
import org.webrtc.VideoSink;
+import org.webrtc.YuvConverter;
import org.webrtc.YuvHelper;
import org.webrtc.audio.JavaAudioDeviceModule;
+import org.webrtc.voiceengine.WebRtcAudioRecord;
+import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -74,24 +83,6 @@ public final class MediaRecorder {
*/
public final VideoSink videoRecoder;
- static {
- final MediaCodecList mediaCodecList = new MediaCodecList(-1);
- for (MediaCodecInfo mediaCodecInfo : mediaCodecList.getCodecInfos()) {
- if (mediaCodecInfo.isEncoder()) {
- final String[] supportedTypes = mediaCodecInfo.getSupportedTypes();
- Log.d(MediaRecorder.class.getSimpleName(), "编码器名称:" + mediaCodecInfo.getName());
- Log.d(MediaRecorder.class.getSimpleName(), "编码器类型:" + String.join(" , ", supportedTypes));
- for (String supportType : supportedTypes) {
- final MediaCodecInfo.CodecCapabilities codecCapabilities = mediaCodecInfo.getCapabilitiesForType(supportType);
- final int[] colorFormats = codecCapabilities.colorFormats;
- Log.d(MediaRecorder.class.getSimpleName(), "编码器格式:" + codecCapabilities.getMimeType());
-// MediaCodecInfo.CodecCapabilities.COLOR_*
- Log.d(MediaRecorder.class.getSimpleName(), "编码器支持格式:" + IntStream.of(colorFormats).boxed().map(String::valueOf).collect(Collectors.joining(" , ")));
- }
- }
- }
- }
-
private MediaRecorder() {
this.executorService = Executors.newFixedThreadPool(2);
this.audioRecoder = audioSamples -> {
@@ -99,12 +90,14 @@ public final class MediaRecorder {
this.videoRecoder = videoFrame -> {
if (this.active && this.videoActive) {
this.executorService.submit(() -> {
+// TextureBufferImpl
// videoFrame.retain();
+ final TextureBufferImpl buffer = (TextureBufferImpl) videoFrame.getBuffer();
final int outputFrameSize = videoFrame.getRotatedWidth() * videoFrame.getRotatedHeight() * 3 / 2;
final ByteBuffer outputFrameBuffer = ByteBuffer.allocateDirect(outputFrameSize);
- final int index = this.videoCodec.dequeueInputBuffer(1000L * 1000);
+ final int index = this.videoCodec.dequeueInputBuffer(1000L * 1000);
// YuvImage:截图
- // YV12
+ // YV12
VideoFrame.I420Buffer i420 = videoFrame.getBuffer().toI420();
// i420.retain();
Log.i(MediaRecorder.class.getSimpleName(), "视频信息:" + videoFrame.getRotatedWidth() + " - " + videoFrame.getRotatedHeight());
@@ -131,16 +124,20 @@ public final class MediaRecorder {
}
public void record(String path) {
+ if(this.active) {
+ return;
+ }
this.record(path, System.currentTimeMillis() + ".mp4", null, null, 1, 1);
}
public void record(String path, String file, String audioFormat, String videoFormat, int width, int height) {
synchronized (MediaRecorder.INSTANCE) {
+ MediaManager.getInstance().initRecord(MediaManager.Type.BACK);
this.file = file;
this.active = true;
if (
this.audioThread == null || !this.audioThread.isAlive() ||
- this.videoThread == null || !this.videoThread.isAlive()
+ this.videoThread == null || !this.videoThread.isAlive()
) {
this.initMediaMuxer(path, file);
this.initAudioThread(MediaFormat.MIMETYPE_AUDIO_AAC, 96000, 44100, 1);
@@ -244,7 +241,7 @@ public final class MediaRecorder {
videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, 800 * 1000);
videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
// videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
- videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar);
+ videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
this.videoCodec.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
} catch (Exception e) {
@@ -325,8 +322,13 @@ public final class MediaRecorder {
private void initMediaMuxer(String path, String file) {
try {
+ final Path filePath = Paths.get(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath(), path, file);
+ final File parentFile = filePath.getParent().toFile();
+ if(!parentFile.exists()) {
+ parentFile.mkdirs();
+ }
this.mediaMuxer = new MediaMuxer(
- Paths.get(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath(), path, file).toAbsolutePath().toString(),
+ filePath.toAbsolutePath().toString(),
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4
);
// 设置方向
diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/Room.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/Room.java
index 610a12b..6a02fbb 100644
--- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/Room.java
+++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/Room.java
@@ -1,9 +1,18 @@
package com.acgist.taoyao.media;
+import android.provider.MediaStore;
import android.util.Log;
+import com.acgist.taoyao.boot.model.Message;
+import com.acgist.taoyao.boot.utils.JSONUtils;
+import com.acgist.taoyao.boot.utils.MapUtils;
+import com.acgist.taoyao.signal.ITaoyao;
+
+import org.webrtc.PeerConnection;
+import org.webrtc.PeerConnectionFactory;
+
import java.io.Closeable;
-import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -13,10 +22,27 @@ import java.util.List;
*/
public class Room implements Closeable {
- private final String id;
+ private final String roomId;
+ private final String password;
+ private final long nativePointer;
+ private final ITaoyao taoyao;
+ private final MediaManager mediaManager;
+ private volatile boolean enter;
+ private PeerConnection.RTCConfiguration rtcConfiguration;
+ private PeerConnectionFactory peerConnectionFactory;
- public Room(String id) {
- this.id = id;
+ public Room(
+ String roomId, String password,
+ boolean audioConsume, boolean videoConsume,
+ boolean audioProduce, boolean videoProduce,
+ long nativePointer, ITaoyao taoyao
+ ) {
+ this.roomId = roomId;
+ this.password = password;
+ this.nativePointer = nativePointer;
+ this.taoyao = taoyao;
+ this.mediaManager = MediaManager.getInstance();
+ this.enter = false;
}
/**
@@ -26,8 +52,51 @@ public class Room implements Closeable {
@Override
public void close() {
- Log.i(Room.class.getSimpleName(), "关闭房间:" + this.id);
+ Log.i(Room.class.getSimpleName(), "关闭房间:" + this.roomId);
+ this.mediaManager.closeClient();
this.remoteClientList.forEach(RemoteClient::close);
}
+ public synchronized void enter() {
+ if(this.enter) {
+ return;
+ }
+ final Message response = this.taoyao.request(this.taoyao.buildMessage("media::router::rtp::capabilities", "roomId", this.roomId));
+ if(response == null) {
+ return;
+ }
+ // STUN | TURN
+ final List iceServers = new ArrayList<>();
+ // TODO:读取配置
+ final PeerConnection.IceServer iceServer = PeerConnection.IceServer.builder("stun:stun1.l.google.com:19302").createIceServer();
+ iceServers.add(iceServer);
+ this.rtcConfiguration = new PeerConnection.RTCConfiguration(iceServers);
+
+ this.rtcConfiguration.screencastMinBitrate = 100;
+ this.rtcConfiguration.enableDtlsSrtp = true;
+
+ this.peerConnectionFactory = this.mediaManager.newClient(MediaManager.Type.BACK);
+ final Object rtpCapabilities = MapUtils.get(response.body(), "rtpCapabilities");
+ this.nativeLoad(this.nativePointer, JSONUtils.toJSON(rtpCapabilities), this.peerConnectionFactory.getNativePeerConnectionFactory(), this.rtcConfiguration);
+ }
+
+ public void enterCallback(String rtpCapabilities, String sctpCapabilities) {
+ this.taoyao.request(this.taoyao.buildMessage(
+ "room::enter",
+ "roomId", this.roomId,
+ "password", this.password,
+ "rtpCapabilities", rtpCapabilities,
+ "sctpCapabilities", sctpCapabilities
+ ));
+ this.enter = true;
+ }
+
+ public void produceMedia() {
+ }
+
+ private native void nativeLoad(long nativePointer, String rtpCapabilities, long peerConnectionFactory, PeerConnection.RTCConfiguration rtcConfiguration);
+ private native void nativeNewClient();
+ private native void nativeCloseClient();
+ private native void nativeCloseRoom(long nativePointer);
+
}
diff --git a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/SessionClient.java b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/SessionClient.java
index 9a33f9e..47ad065 100644
--- a/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/SessionClient.java
+++ b/taoyao-client-android/taoyao/media/src/main/java/com/acgist/taoyao/media/SessionClient.java
@@ -9,6 +9,7 @@ import com.acgist.taoyao.signal.ITaoyao;
import org.webrtc.DataChannel;
import org.webrtc.IceCandidate;
+import org.webrtc.JniCommon;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
diff --git a/taoyao-client-web/src/components/Taoyao.js b/taoyao-client-web/src/components/Taoyao.js
index 0fbdeba..9b319c6 100644
--- a/taoyao-client-web/src/components/Taoyao.js
+++ b/taoyao-client-web/src/components/Taoyao.js
@@ -1415,13 +1415,21 @@ class Taoyao extends RemoteClient {
return;
}
me.roomId = roomId;
- me.mediasoupDevice = new mediasoupClient.Device();
const response = await me.request(
protocol.buildMessage("media::router::rtp::capabilities", {
roomId: me.roomId,
})
);
const routerRtpCapabilities = response.body.rtpCapabilities;
+ me.mediasoupDevice = new mediasoupClient.Device();
+// mediasoupClient.parseScalabilityMode("L2T3");
+// // => { spatialLayers: 2, temporalLayers: 3 }
+// mediasoupClient.parseScalabilityMode("S3T3");
+// // => { spatialLayers: 3, temporalLayers: 3 }
+// mediasoupClient.parseScalabilityMode("L4T7_KEY_SHIFT");
+// // => { spatialLayers: 4, temporalLayers: 7 }
+// mediasoupClient.parseScalabilityMode(undefined);
+// // => { spatialLayers: 1, temporalLayers: 1 }
await me.mediasoupDevice.load({ routerRtpCapabilities });
await me.request(
protocol.buildMessage("room::enter", {
@@ -1916,7 +1924,7 @@ class Taoyao extends RemoteClient {
this.videoProducer = null;
});
this.videoProducer.on("trackended", () => {
- console.warn("video producer trackended", this.audioProducer);
+ console.warn("video producer trackended", this.videoProducer);
this.closeVideoProducer().catch(() => {});
});
} catch (error) {
diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteProperties.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteProperties.java
index 99b1262..f310f06 100644
--- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteProperties.java
+++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteProperties.java
@@ -10,7 +10,6 @@ import lombok.Setter;
/**
* 地址重写
- * 内外网多网卡环境重写网络号保留主机号,通过重新地址实现多网互通。
*
* @author acgist
*/
diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteRuleProperties.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteRuleProperties.java
index 8df45f4..6f26d61 100644
--- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteRuleProperties.java
+++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/IpRewriteRuleProperties.java
@@ -6,11 +6,7 @@ import lombok.Setter;
/**
* 重写规则
- *
- * @author acgist
- */
-/**
- * WebRTC STUN配置
+ * 没有配置内网地址等于网络号加上原始主机号
*
* @author acgist
*/
@@ -21,7 +17,9 @@ public class IpRewriteRuleProperties {
@Schema(title = "网络号", description = "网络号:匹配终端IP")
private String network;
- @Schema(title = "目标地址", description = "目标地址:没有配置等于网络号加上原始主机号")
- private String targetHost;
+ @Schema(title = "内网地址", description = "内网地址")
+ private String innerHost;
+ @Schema(title = "外网地址", description = "外网地址")
+ private String outerHost;
}
diff --git a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/NetUtils.java b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/NetUtils.java
index a1fd0ba..2d8981a 100644
--- a/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/NetUtils.java
+++ b/taoyao-signal-server/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/NetUtils.java
@@ -79,7 +79,9 @@ public final class NetUtils {
try {
final InetAddress sourceAddress = NetUtils.realAddress(sourceIp);
final InetAddress clientAddress = NetUtils.realAddress(clientIp);
- if(NetUtils.localAddress(sourceAddress) && NetUtils.localAddress(clientAddress)) {
+ final boolean sourceLocal = NetUtils.localAddress(sourceAddress);
+ final boolean clientLocal = NetUtils.localAddress(clientAddress);
+ if(sourceLocal && clientLocal) {
final byte[] sourceBytes = sourceAddress.getAddress();
final byte[] clientBytes = clientAddress.getAddress();
final int length = (sourceBytes.length & clientBytes.length) * Byte.SIZE;
@@ -109,21 +111,25 @@ public final class NetUtils {
if(Boolean.FALSE.equals(NetUtils.ipRewriteProperties.getEnabled())) {
return sourceIp;
}
- log.debug("重写地址:{} - {}", sourceIp, clientIp);
+ final IpRewriteRuleProperties rule = NetUtils.ipRewriteProperties.getRule().stream()
+ .filter(v -> NetUtils.subnetIp(v.getNetwork(), clientIp))
+ .findFirst()
+ .orElse(null);
+ if(rule == null) {
+ return sourceIp;
+ }
+ log.debug("地址重写:{} - {} - {}", sourceIp, clientIp, rule.getNetwork());
try {
final InetAddress sourceAddress = NetUtils.realAddress(sourceIp);
final InetAddress clientAddress = NetUtils.realAddress(clientIp);
- if(NetUtils.localAddress(sourceAddress) && NetUtils.localAddress(clientAddress)) {
- final IpRewriteRuleProperties rule = NetUtils.ipRewriteProperties.getRule().stream()
- .filter(v -> NetUtils.subnetIp(v.getNetwork(), clientIp))
- .findFirst()
- .orElse(null);
- if(rule == null) {
- return sourceIp;
- }
- if(StringUtils.isNotEmpty(rule.getTargetHost())) {
- return rule.getTargetHost();
+ final boolean sourceLocal = NetUtils.localAddress(sourceAddress);
+ final boolean clientLocal = NetUtils.localAddress(clientAddress);
+ if(sourceLocal && clientLocal) {
+ // 明确配置
+ if(StringUtils.isNotEmpty(rule.getInnerHost())) {
+ return rule.getInnerHost();
}
+ // 地址 = 网络号 + 主机号
final byte[] sourceBytes = sourceAddress.getAddress();
final byte[] clientBytes = clientAddress.getAddress();
final int length = (sourceBytes.length & clientBytes.length) * Byte.SIZE;
@@ -142,8 +148,16 @@ public final class NetUtils {
return InetAddress.getByAddress(bytes).getHostAddress();
}
}
+ // 公网服务 && 内网设备
+ if(sourceLocal && !clientLocal && StringUtils.isNotEmpty(rule.getInnerHost())) {
+ return rule.getInnerHost();
+ }
+ // 内网服务 && 公网设备
+ if(!sourceLocal && clientLocal && StringUtils.isNotEmpty(rule.getOuterHost())) {
+ return rule.getOuterHost();
+ }
} catch (UnknownHostException e) {
- log.error("IP地址转换异常:{}-{}", sourceIp, clientIp, e);
+ log.error("地址重写异常:{}-{}", sourceIp, clientIp, e);
}
return sourceIp;
}
diff --git a/taoyao-signal-server/taoyao-server/src/main/resources/application.yml b/taoyao-signal-server/taoyao-server/src/main/resources/application.yml
index d604491..856cbaf 100644
--- a/taoyao-signal-server/taoyao-server/src/main/resources/application.yml
+++ b/taoyao-signal-server/taoyao-server/src/main/resources/application.yml
@@ -230,12 +230,12 @@ taoyao:
enabled: true
prefix: 24
rule:
- - prefix: 24
- network: 192.168.1.0
- target-host:
- - prefix: 24
- network: 192.168.8.0
- target-host:
+ - network: 192.168.1.0
+ inner-host:
+ outer-host:
+ - network: 192.168.8.0
+ inner-host:
+ outer-host:
# 脚本配置
script:
enabled: true
diff --git a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionCallProtocol.java b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionCallProtocol.java
index e2932d4..ed51752 100644
--- a/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionCallProtocol.java
+++ b/taoyao-signal-server/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/session/SessionCallProtocol.java
@@ -55,8 +55,8 @@ public class SessionCallProtocol extends ProtocolSessionAdapter {
Constant.NAME, client.status().getName(),
Constant.CLIENT_ID, client.clientId(),
Constant.SESSION_ID, session.getId(),
- Constant.AUDIO, MapUtils.get(body, Constant.AUDIO),
- Constant.VIDEO, MapUtils.get(body, Constant.VIDEO)
+ Constant.AUDIO, MapUtils.get(body, Constant.AUDIO, true),
+ Constant.VIDEO, MapUtils.get(body, Constant.VIDEO, true)
));
target.push(callMessage);
}