[*] 写了一点

This commit is contained in:
acgist
2023-03-30 08:39:07 +08:00
parent aa395ff9c7
commit d8a1121308
40 changed files with 466 additions and 55 deletions

View File

@@ -1,11 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
@@ -18,6 +13,7 @@
android:name=".MainActivity"
android:exported="true"
android:label="@string/title_activity_main"
android:launchMode="singleTask"
android:theme="@style/Theme.Taoyao">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@@ -1,6 +1,8 @@
package com.acgist.taoyao.client;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -9,6 +11,7 @@ import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
@@ -31,6 +34,8 @@ public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle bundle) {
Log.i(MainActivity.class.getSimpleName(), "onCreate");
super.onCreate(bundle);
// 请求权限
this.requestPermission();
// 启动点亮屏幕
this.setTurnScreenOn(true);
// 锁屏显示屏幕
@@ -64,6 +69,35 @@ public class MainActivity extends AppCompatActivity {
super.onDestroy();
}
/**
* 请求权限
*/
private void requestPermission() {
final String[] permissions = new String[]{
Manifest.permission.CAMERA,
Manifest.permission.INTERNET,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.RECEIVE_BOOT_COMPLETED,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
boolean allGranted = true;
for (String permission : permissions) {
if(this.getApplicationContext().checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {
Log.i(MediaService.class.getSimpleName(), "授权成功:" + permission);
} else {
allGranted = false;
Log.w(MediaService.class.getSimpleName(), "授权失败:" + permission);
}
}
if(!allGranted) {
Toast.makeText(this, "授权失败", Toast.LENGTH_SHORT).show();
}
}
/**
* 拉起媒体服务
*/
@@ -81,7 +115,6 @@ public class MainActivity extends AppCompatActivity {
}
intent.setAction(MediaService.Action.CONNECT.name());
intent.putExtra("mainHandler", this.mainHandler);
intent.setAction("connect");
this.startService(intent);
} else {
Log.w(MainActivity.class.getSimpleName(), "拉起媒体服务失败");

View File

@@ -1,9 +1,11 @@
package com.acgist.taoyao.client;
import android.Manifest;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.location.LocationManager;
import android.net.wifi.WifiManager;
@@ -59,15 +61,15 @@ public class MediaService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(MediaService.class.getSimpleName(), "onStartCommand");
if(Action.CONNECT.name().equals(intent.getAction())) {
if(this.taoyao == null) {
if (Action.CONNECT.name().equals(intent.getAction())) {
if (this.taoyao == null) {
Log.d(MediaService.class.getSimpleName(), "打开信令连接");
this.mainHandler = (Handler) intent.getSerializableExtra("mainHandler");
this.connect();
} else {
Log.d(MediaService.class.getSimpleName(), "信令已经连接");
}
} else if(Action.RECONNECT.name().equals(intent.getAction())) {
} else if (Action.RECONNECT.name().equals(intent.getAction())) {
Log.d(MediaService.class.getSimpleName(), "重新连接信令");
this.connect();
} else {
@@ -114,7 +116,7 @@ public class MediaService extends Service {
}
private synchronized void close() {
if(this.taoyao == null) {
if (this.taoyao == null) {
return;
}
Toast.makeText(this.getApplicationContext(), "关闭信令", Toast.LENGTH_SHORT).show();

View File

@@ -20,7 +20,7 @@ import com.acgist.taoyao.boot.model.MessageCode;
import com.acgist.taoyao.boot.model.MessageCodeException;
import com.acgist.taoyao.boot.utils.CloseableUtils;
import com.acgist.taoyao.boot.utils.JSONUtils;
import com.acgist.taoyao.media.ClientRecorder;
import com.acgist.taoyao.media.MediaRecorder;
import com.acgist.taoyao.client.utils.IdUtils;
import org.apache.commons.lang3.ArrayUtils;
@@ -499,7 +499,7 @@ public final class Taoyao {
"signal", this.wifiSignal(),
"battery", this.battery(),
"charging", this.charging(),
"recording", ClientRecorder.getInstance().isActive()
"recording", MediaRecorder.getInstance().isActive()
));
}
@@ -530,7 +530,7 @@ public final class Taoyao {
"signal", this.wifiSignal(),
"battery", this.battery(),
"charging", this.charging(),
"recording", ClientRecorder.getInstance().isActive()
"recording", MediaRecorder.getInstance().isActive()
));
}

View File

@@ -12,4 +12,7 @@
<string name="encrypt">DES</string>
<!-- 信令加密密钥 -->
<string name="encryptSecret">2SPWy+TF1zM=</string>
<!-- 文件存储目录 -->
<string name="storagePathImage">/taoyao/image</string>
<string name="storagePathVideo">/taoyao/video</string>
</resources>

View File

@@ -25,10 +25,24 @@ set(
set(
SOURCE_FILES
${SOURCE_DIR}/rtp.hpp
${SOURCE_DIR}/rtp.cpp
${SOURCE_DIR}/webrtc.hpp
${SOURCE_DIR}/webrtc.cpp
${SOURCE_DIR}/include/LocalClient.hpp
${SOURCE_DIR}/include/MediaRecorder.hpp
${SOURCE_DIR}/include/MediasoupClient.hpp
${SOURCE_DIR}/include/P2PClient.hpp
${SOURCE_DIR}/include/RemoteClient.hpp
${SOURCE_DIR}/include/Room.hpp
${SOURCE_DIR}/include/RtpAudioPublisher.hpp
${SOURCE_DIR}/include/RtpClient.hpp
${SOURCE_DIR}/include/RtpVideoPublisher.hpp
${SOURCE_DIR}/media/LocalClient.cpp
${SOURCE_DIR}/media/MediaRecorder.cpp
${SOURCE_DIR}/media/P2PClient.cpp
${SOURCE_DIR}/media/RemoteClient.cpp
${SOURCE_DIR}/media/Room.cpp
${SOURCE_DIR}/rtp/RtpAudioPublisher.cpp
${SOURCE_DIR}/rtp/RtpClient.cpp
${SOURCE_DIR}/rtp/RtpVideoPublisher.cpp
${SOURCE_DIR}/webrtc/MediasoupClient.cpp
)
set(LIBWEBRTC_BINARY_PATH ${LIBWEBRTC_BINARY_PATH}/${ANDROID_ABI} CACHE STRING "libwebrtc binary path" FORCE)

View File

@@ -4,6 +4,11 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>

View File

@@ -1,10 +0,0 @@
#pragma once
// 通道类型rtp
#define TRANSPORT_RTP "RTP"
// 通道类型webrtc
#define TRANSPORT_WEBRTC "WEBRTC"
namespace acgist {
}

View File

@@ -1,5 +1,5 @@
#pragma once
namespace acgist {
}

View File

@@ -0,0 +1,5 @@
#pragma once
namespace acgist {
}

View File

@@ -0,0 +1,5 @@
#pragma once
namespace acgist {
}

View File

@@ -0,0 +1,5 @@
#pragma once
namespace acgist {
}

View File

@@ -0,0 +1,5 @@
#pragma once
namespace acgist {
}

View File

@@ -0,0 +1,5 @@
#pragma once
namespace acgist {
}

View File

@@ -0,0 +1,5 @@
#pragma once
namespace acgist {
}

View File

@@ -0,0 +1,7 @@
#include <jni.h>
#include "LocalClient.hpp"
namespace acgist {
}

View File

@@ -0,0 +1,5 @@
#include "MediaRecorder.hpp"
namespace acgist {
}

View File

@@ -0,0 +1,5 @@
#include "P2PClient.hpp"
namespace acgist {
}

View File

@@ -0,0 +1,5 @@
#include "RemoteClient.hpp"
namespace acgist {
}

View File

@@ -0,0 +1,5 @@
#include "Room.hpp"
namespace acgist {
}

View File

@@ -1 +0,0 @@
#pragma once

View File

@@ -0,0 +1,5 @@
#include "RtpAudioPublisher.hpp"
namespace acgist {
}

View File

@@ -0,0 +1,5 @@
#include "RtpClient.hpp"
namespace acgist {
}

View File

@@ -0,0 +1,5 @@
#include "RtpVideoPublisher.hpp"
namespace acgist {
}

View File

@@ -1 +0,0 @@
#include <jni.h>

View File

@@ -1 +0,0 @@
#pragma once

View File

@@ -1,5 +0,0 @@
#include "../include/AudioPublisher.hpp"
namespace acgist {
}

View File

@@ -1,5 +0,0 @@
#include "../include/MediaRecorder.hpp"
namespace acgist {
}

View File

@@ -0,0 +1,5 @@
#include "MediasoupClient.hpp"
namespace acgist {
}

View File

@@ -1,5 +0,0 @@
#include "../include/VideoPublisher.hpp"
namespace acgist {
}

View File

@@ -6,8 +6,7 @@ package com.acgist.taoyao.media;
*
* @author acgist
*/
public class RoomClient {
public class LocalClient {
}

View File

@@ -0,0 +1,310 @@
package com.acgist.taoyao.media;
import android.content.Context;
import android.util.Log;
import org.webrtc.AudioSource;
import org.webrtc.AudioTrack;
import org.webrtc.Camera2Enumerator;
import org.webrtc.CameraEnumerator;
import org.webrtc.CameraVideoCapturer;
import org.webrtc.EglBase;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.SurfaceTextureHelper;
import org.webrtc.SurfaceViewRenderer;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;
import org.webrtc.voiceengine.WebRtcAudioUtils;
import java.util.Iterator;
import java.util.List;
/**
* 媒体来源管理器
*
* @author acgist
*/
public class MediaManager {
/**
* 来源类型
*
* @author acgist
*/
public enum Type {
// 后置摄像头
BACK,
// 前置摄像头
FRONT,
// 屏幕共享
SCREEN;
}
/**
* 视频类型
*/
private Type type;
/**
* 上下文
*/
private Context context;
/**
* 媒体流:声音、主码流(预览流)、次码流
*/
private MediaStream mediaStream;
/**
* 屏幕捕获
*/
private VideoCapturer videoCapturer;
/**
* 摄像头捕获
*/
private CameraVideoCapturer cameraVideoCapturer;
/**
* Peer连接工厂
*/
private PeerConnectionFactory peerConnectionFactory;
/**
* 预览
*/
private SurfaceViewRenderer previewView;
static {
// 设置采样
// WebRtcAudioUtils.setDefaultSampleRateHz();
// 噪声消除
WebRtcAudioUtils.setWebRtcBasedNoiseSuppressor(true);
// 回声小丑
WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true);
}
/**
* 加载媒体流
*/
public void init() {
this.peerConnectionFactory = PeerConnectionFactory.builder().createPeerConnectionFactory();
this.mediaStream = this.peerConnectionFactory.createLocalMediaStream("ARDAMS");
}
/**
* 切换视频来源
*
* @param type 来源类型
*
* TODO设置分享
*/
public void exchange(Type type) {
if(this.type == type) {
return;
}
this.type = type;
Log.i(MediaManager.class.getSimpleName(), "设置视频来源:" + type);
if(this.type == Type.SCREEN || type == Type.SCREEN) {
this.initVideo();
} else {
// TODO测试是否需要完全重置
this.cameraVideoCapturer.switchCamera(new CameraVideoCapturer.CameraSwitchHandler() {
@Override
public void onCameraSwitchDone(boolean success) {
}
@Override
public void onCameraSwitchError(String message) {
}
});
}
}
/**
* 加载音频
*/
private void initAudio() {
// 关闭音频
this.closeAudioTrack();
// 加载音频
final MediaConstraints mediaConstraints = new MediaConstraints();
final AudioSource audioSource = this.peerConnectionFactory.createAudioSource(mediaConstraints);
final AudioTrack audioTrack = this.peerConnectionFactory.createAudioTrack("ARDAMSa0", audioSource);
audioTrack.setEnabled(true);
this.mediaStream.addTrack(audioTrack);
Log.i(MediaManager.class.getSimpleName(), "加载音频:" + audioTrack.id());
}
/**
* 加载视频
*/
private void initVideo() {
this.closeVideoTrack();
if(this.cameraVideoCapturer != null) {
this.cameraVideoCapturer.dispose();
}
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.cameraVideoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler());
} else if(this.type == Type.BACK && cameraEnumerator.isBackFacing(name)) {
Log.i(MediaManager.class.getSimpleName(), "加载后置摄像头:" + name);
this.cameraVideoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler());
} else {
Log.i(MediaManager.class.getSimpleName(), "忽略摄像头:" + name);
}
}
}
/**
* 加载视频
*/
private void initVideoTrack() {
SurfaceTextureHelper surfaceTextureHelper = null;
// 设置预览
if(this.previewView != null) {
final EglBase eglBase = EglBase.create();
surfaceTextureHelper = SurfaceTextureHelper.create("MediaVideoThread", eglBase.getEglBaseContext());
this.previewView.setMirror(true);
this.previewView.setEnableHardwareScaler(true);
}
// 是否捕获屏幕
final boolean screen = this.type == Type.SCREEN;
final VideoSource videoSource = this.peerConnectionFactory.createVideoSource(screen);
if(screen) {
Log.i(MediaManager.class.getSimpleName(), "捕获屏幕");
} else {
Log.i(MediaManager.class.getSimpleName(), "捕获摄像头");
this.cameraVideoCapturer.initialize(surfaceTextureHelper, this.context, videoSource.getCapturerObserver());
this.cameraVideoCapturer.startCapture(640, 480, 30);
}
final VideoTrack videoTrack = this.peerConnectionFactory.createVideoTrack("ARDAMSv0", videoSource);
videoTrack.setEnabled(true);
this.mediaStream.addTrack(videoTrack);
Log.i(MediaManager.class.getSimpleName(), "加载视频:" + videoTrack.id());
}
private void initPreview() {
}
public void pauseAudio() {
synchronized (this.mediaStream.audioTracks) {
this.mediaStream.audioTracks.forEach(a -> a.setEnabled(false));
}
}
public void resumeAudio() {
synchronized (this.mediaStream.audioTracks) {
this.mediaStream.audioTracks.forEach(a -> a.setEnabled(true));
}
}
public void pauseVideo() {
synchronized (this.mediaStream.videoTracks) {
this.mediaStream.videoTracks.forEach(v -> v.setEnabled(false));
}
synchronized (this.mediaStream.preservedVideoTracks) {
this.mediaStream.preservedVideoTracks.forEach(v -> v.setEnabled(false));
}
}
public void resumeVideo() {
synchronized (this.mediaStream.videoTracks) {
this.mediaStream.videoTracks.forEach(v -> v.setEnabled(true));
}
synchronized (this.mediaStream.preservedVideoTracks) {
this.mediaStream.preservedVideoTracks.forEach(v -> v.setEnabled(true));
}
}
/**
* 关闭声音
*/
private void closeAudioTrack() {
synchronized (this.mediaStream.audioTracks) {
AudioTrack track;
final Iterator<AudioTrack> iterator = this.mediaStream.audioTracks.iterator();
while(iterator.hasNext()) {
track = iterator.next();
iterator.remove();
track.dispose();
}
}
}
/**
* 关闭视频
*/
private void closeVideoTrack() {
// 次码流
this.closeVideoTrack(this.mediaStream.videoTracks);
// 主码流
this.closeVideoTrack(this.mediaStream.preservedVideoTracks);
}
/**
* 关闭视频
*
* @param list 视频列表
*/
private void closeVideoTrack(List<VideoTrack> list) {
synchronized (list) {
VideoTrack track;
final Iterator<VideoTrack> iterator = list.iterator();
while(iterator.hasNext()) {
track = iterator.next();
iterator.remove();
track.dispose();
}
}
}
/**
* 释放资源
*/
public void close() {
if(this.cameraVideoCapturer != null) {
this.cameraVideoCapturer.dispose();
}
if(this.mediaStream != null) {
this.mediaStream.dispose();
}
if(this.peerConnectionFactory != null) {
this.peerConnectionFactory.dispose();
}
}
/**
* 摄像头事件
*
* @author acgist
*/
private static class MediaCameraEventsHandler implements CameraVideoCapturer.CameraEventsHandler {
@Override
public void onCameraError(String message) {
}
@Override
public void onCameraDisconnected() {
}
@Override
public void onCameraFreezed(String message) {
}
@Override
public void onCameraOpening(String message) {
}
@Override
public void onFirstFrameAvailable() {
}
@Override
public void onCameraClosed() {
}
}
}

View File

@@ -5,16 +5,16 @@ package com.acgist.taoyao.media;
*
* @author acgist
*/
public final class ClientRecorder {
public final class MediaRecorder {
/**
* 是否正在录像
*/
private boolean active;
private static final ClientRecorder INSTANCE = new ClientRecorder();
private static final MediaRecorder INSTANCE = new MediaRecorder();
public static final ClientRecorder getInstance() {
public static final MediaRecorder getInstance() {
return INSTANCE;
}