[+] android github action
This commit is contained in:
28
.github/workflows/build.yml
vendored
28
.github/workflows/build.yml
vendored
@@ -5,7 +5,7 @@ on:
|
|||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
taoyao-snail-server:
|
taoyao-signal-server:
|
||||||
name: Build taoyao signal server
|
name: Build taoyao signal server
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
@@ -13,9 +13,9 @@ jobs:
|
|||||||
runs-on: ${{ matrix.runs-on }}
|
runs-on: ${{ matrix.runs-on }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
- name: Build with Maven
|
- name: Build with Maven
|
||||||
@@ -61,4 +61,24 @@ jobs:
|
|||||||
# 不能直接运行
|
# 不能直接运行
|
||||||
# npm run dev
|
# npm run dev
|
||||||
working-directory: ./taoyao-client-media
|
working-directory: ./taoyao-client-media
|
||||||
|
taoyao-client-android:
|
||||||
|
name: Build taoyao client android
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
runs-on: [ macos-latest, ubuntu-latest, windows-latest ]
|
||||||
|
runs-on: ${{ matrix.runs-on }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
java-version: 17
|
||||||
|
- name: Setup Android SDK
|
||||||
|
uses: android-actions/setup-android@v2
|
||||||
|
- name: Build with Gradle
|
||||||
|
if: runner.os != 'windows'
|
||||||
|
run: ./taoyao-client-android/taoyao/gradlew --no-daemon assembleRelease
|
||||||
|
- name: Build with Gradle
|
||||||
|
if: runner.os == 'windows'
|
||||||
|
run: ./taoyao-client-android/taoyao/gradlew.bat --no-daemon assembleRelease
|
||||||
|
|||||||
@@ -156,21 +156,21 @@ public final class Taoyao implements ITaoyao {
|
|||||||
*/
|
*/
|
||||||
private final Map<Long, Message> requestMessage;
|
private final Map<Long, Message> requestMessage;
|
||||||
/**
|
/**
|
||||||
* 循环消息Handler
|
* 消息Handler
|
||||||
*/
|
*/
|
||||||
private final Handler loopMessageHandler;
|
private final Handler messageHandler;
|
||||||
/**
|
/**
|
||||||
* 循环消息线程
|
* 消息线程
|
||||||
*/
|
*/
|
||||||
private final HandlerThread loopMessageThread;
|
private final HandlerThread messageThread;
|
||||||
/**
|
/**
|
||||||
* 消息处理Handler
|
* 执行Handler
|
||||||
*/
|
*/
|
||||||
private final Handler executeMessageHandler;
|
private final Handler executeHandler;
|
||||||
/**
|
/**
|
||||||
* 消息处理线程
|
* 执行线程
|
||||||
*/
|
*/
|
||||||
private final HandlerThread executeMessageThread;
|
private final HandlerThread executeThread;
|
||||||
/**
|
/**
|
||||||
* 心跳Handler
|
* 心跳Handler
|
||||||
*/
|
*/
|
||||||
@@ -224,15 +224,15 @@ public final class Taoyao implements ITaoyao {
|
|||||||
this.batteryManager = context.getSystemService(BatteryManager.class);
|
this.batteryManager = context.getSystemService(BatteryManager.class);
|
||||||
this.locationManager = context.getSystemService(LocationManager.class);
|
this.locationManager = context.getSystemService(LocationManager.class);
|
||||||
this.requestMessage = new ConcurrentHashMap<>();
|
this.requestMessage = new ConcurrentHashMap<>();
|
||||||
this.loopMessageThread = new HandlerThread("LoopMessageThread");
|
this.messageThread = new HandlerThread("MessageThread");
|
||||||
this.loopMessageThread.setDaemon(true);
|
this.messageThread.setDaemon(true);
|
||||||
this.loopMessageThread.start();
|
this.messageThread.start();
|
||||||
this.loopMessageHandler = new Handler(this.loopMessageThread.getLooper());
|
this.messageHandler = new Handler(this.messageThread.getLooper());
|
||||||
this.loopMessageHandler.post(this::loopMessage);
|
this.messageHandler.post(this::connect);
|
||||||
this.executeMessageThread = new HandlerThread("ExecuteMessageThread");
|
this.executeThread = new HandlerThread("ExecuteThread");
|
||||||
this.executeMessageThread.setDaemon(true);
|
this.executeThread.setDaemon(true);
|
||||||
this.executeMessageThread.start();
|
this.executeThread.start();
|
||||||
this.executeMessageHandler = new Handler(this.executeMessageThread.getLooper());
|
this.executeHandler = new Handler(this.executeThread.getLooper());
|
||||||
this.heartbeatThread = new HandlerThread("HeartbeatThread");
|
this.heartbeatThread = new HandlerThread("HeartbeatThread");
|
||||||
this.heartbeatThread.setDaemon(true);
|
this.heartbeatThread.setDaemon(true);
|
||||||
this.heartbeatThread.start();
|
this.heartbeatThread.start();
|
||||||
@@ -281,26 +281,20 @@ public final class Taoyao implements ITaoyao {
|
|||||||
// socket.setSoTimeout(this.timeout);
|
// socket.setSoTimeout(this.timeout);
|
||||||
this.socket.connect(new InetSocketAddress(this.host, this.port), this.timeout);
|
this.socket.connect(new InetSocketAddress(this.host, this.port), this.timeout);
|
||||||
if (this.socket.isConnected()) {
|
if (this.socket.isConnected()) {
|
||||||
|
this.connect = true;
|
||||||
this.input = this.socket.getInputStream();
|
this.input = this.socket.getInputStream();
|
||||||
this.output = this.socket.getOutputStream();
|
this.output = this.socket.getOutputStream();
|
||||||
this.clientRegister();
|
this.clientRegister();
|
||||||
this.connect = true;
|
|
||||||
this.taoyaoListener.onConnected();
|
this.taoyaoListener.onConnected();
|
||||||
synchronized (this) {
|
this.messageHandler.post(this::pull);
|
||||||
this.notifyAll();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.connect = false;
|
this.connect = false;
|
||||||
synchronized (this) {
|
this.messageHandler.postDelayed(this::connect, this.timeout);
|
||||||
try {
|
|
||||||
this.wait(this.timeout);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.d(Taoyao.class.getSimpleName(), "信令等待异常", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(Taoyao.class.getSimpleName(), "连接信令异常:" + this.host + ":" + this.port, e);
|
Log.e(Taoyao.class.getSimpleName(), "连接信令异常:" + this.host + ":" + this.port, e);
|
||||||
|
this.connect = false;
|
||||||
|
this.messageHandler.postDelayed(this::connect, this.timeout);
|
||||||
}
|
}
|
||||||
return this.connect;
|
return this.connect;
|
||||||
}
|
}
|
||||||
@@ -308,17 +302,13 @@ public final class Taoyao implements ITaoyao {
|
|||||||
/**
|
/**
|
||||||
* 循环读取信令消息
|
* 循环读取信令消息
|
||||||
*/
|
*/
|
||||||
private void loopMessage() {
|
private void pull() {
|
||||||
int length = 0;
|
int length = 0;
|
||||||
short messageLength = 0;
|
short messageLength = 0;
|
||||||
final byte[] bytes = new byte[1024];
|
final byte[] bytes = new byte[1024];
|
||||||
final ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
|
final ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
|
||||||
while (!this.close) {
|
while (!this.close && this.connect) {
|
||||||
try {
|
try {
|
||||||
// 重连
|
|
||||||
while (!this.close && !this.connect) {
|
|
||||||
this.connect();
|
|
||||||
}
|
|
||||||
// 读取
|
// 读取
|
||||||
while (!this.close && (length = this.input.read(bytes)) >= 0) {
|
while (!this.close && (length = this.input.read(bytes)) >= 0) {
|
||||||
buffer.put(bytes, 0, length);
|
buffer.put(bytes, 0, length);
|
||||||
@@ -362,6 +352,9 @@ public final class Taoyao implements ITaoyao {
|
|||||||
this.disconnect();
|
this.disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!this.close && !this.connect) {
|
||||||
|
this.messageHandler.post(this::connect);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -461,8 +454,8 @@ public final class Taoyao implements ITaoyao {
|
|||||||
this.close = true;
|
this.close = true;
|
||||||
this.disconnect();
|
this.disconnect();
|
||||||
this.heartbeatThread.quitSafely();
|
this.heartbeatThread.quitSafely();
|
||||||
this.loopMessageThread.quitSafely();
|
this.messageThread.quitSafely();
|
||||||
this.executeMessageThread.quitSafely();
|
this.executeThread.quitSafely();
|
||||||
this.rooms.values().forEach(Room::close);
|
this.rooms.values().forEach(Room::close);
|
||||||
this.sessionClients.values().forEach(SessionClient::close);
|
this.sessionClients.values().forEach(SessionClient::close);
|
||||||
}
|
}
|
||||||
@@ -525,7 +518,7 @@ public final class Taoyao implements ITaoyao {
|
|||||||
request.notifyAll();
|
request.notifyAll();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.executeMessageHandler.post(() -> this.dispatch(content, header, message));
|
this.executeHandler.post(() -> this.dispatch(content, header, message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.media.projection.MediaProjection;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.acgist.taoyao.media.client.PhotographClient;
|
import com.acgist.taoyao.media.client.PhotographClient;
|
||||||
import com.acgist.taoyao.media.client.RecordClient;
|
import com.acgist.taoyao.media.client.RecordClient;
|
||||||
@@ -87,7 +88,13 @@ public final class MediaManager {
|
|||||||
* 视频质量
|
* 视频质量
|
||||||
*/
|
*/
|
||||||
private String videoQuantity;
|
private String videoQuantity;
|
||||||
|
/**
|
||||||
|
* 通道数量
|
||||||
|
*/
|
||||||
private int channelCount;
|
private int channelCount;
|
||||||
|
/**
|
||||||
|
* 关键帧频率
|
||||||
|
*/
|
||||||
private int iFrameInterval;
|
private int iFrameInterval;
|
||||||
/**
|
/**
|
||||||
* 视频来源类型
|
* 视频来源类型
|
||||||
@@ -220,10 +227,13 @@ public final class MediaManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 是否可用
|
* @return 是否可用:所有配置加载完成
|
||||||
*/
|
*/
|
||||||
public boolean available() {
|
public boolean available() {
|
||||||
return this.mainHandler != null && this.context != null && this.taoyao != null;
|
return
|
||||||
|
this.taoyao != null &&
|
||||||
|
this.context != null &&
|
||||||
|
this.mediaProperties != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -265,7 +275,8 @@ public final class MediaManager {
|
|||||||
*/
|
*/
|
||||||
public PeerConnectionFactory newClient(VideoSourceType videoSourceType) {
|
public PeerConnectionFactory newClient(VideoSourceType videoSourceType) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
while(this.mediaProperties == null) {
|
while(!this.available()) {
|
||||||
|
Log.i(MediaManager.class.getSimpleName(), "等待配置");
|
||||||
try {
|
try {
|
||||||
this.wait(1000);
|
this.wait(1000);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
@@ -409,14 +420,14 @@ public final class MediaManager {
|
|||||||
// 高音过滤
|
// 高音过滤
|
||||||
mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googHighpassFilter", "true"));
|
mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googHighpassFilter", "true"));
|
||||||
// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAudioMirroring", "false"));
|
// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAudioMirroring", "false"));
|
||||||
// 自动增益
|
// 自动增益:AGC
|
||||||
mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAutoGainControl", "true"));
|
mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAutoGainControl", "true"));
|
||||||
// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAutoGainControl2", "true"));
|
// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googAutoGainControl2", "true"));
|
||||||
// 回声消除
|
// 回声消除:AEC
|
||||||
mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googEchoCancellation", "true"));
|
mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googEchoCancellation", "true"));
|
||||||
// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googEchoCancellation2", "true"));
|
// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googEchoCancellation2", "true"));
|
||||||
// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googDAEchoCancellation", "true"));
|
// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googDAEchoCancellation", "true"));
|
||||||
// 噪音处理
|
// 噪音处理:NS
|
||||||
mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googNoiseSuppression", "true"));
|
mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googNoiseSuppression", "true"));
|
||||||
// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googNoiseSuppression2", "true"));
|
// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googNoiseSuppression2", "true"));
|
||||||
// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googTypingNoiseDetection", "true"));
|
// mediaConstraints.mandatory.add(new MediaConstraints.KeyValuePair("googTypingNoiseDetection", "true"));
|
||||||
@@ -545,11 +556,7 @@ public final class MediaManager {
|
|||||||
public void updateVideoConfig(MediaVideoProperties mediaVideoProperties) {
|
public void updateVideoConfig(MediaVideoProperties mediaVideoProperties) {
|
||||||
this.mediaVideoProperties = mediaVideoProperties;
|
this.mediaVideoProperties = mediaVideoProperties;
|
||||||
if(this.videoCapturer != null) {
|
if(this.videoCapturer != null) {
|
||||||
try {
|
this.stopCapture("次码流", this.videoCapturer);
|
||||||
this.videoCapturer.stopCapture();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e(MediaManager.class.getSimpleName(), "暂停视频捕获异常", e);
|
|
||||||
}
|
|
||||||
this.videoCapturer.startCapture(this.mediaVideoProperties.getWidth(), this.mediaVideoProperties.getHeight(), this.mediaVideoProperties.getFrameRate());
|
this.videoCapturer.startCapture(this.mediaVideoProperties.getWidth(), this.mediaVideoProperties.getHeight(), this.mediaVideoProperties.getFrameRate());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -615,11 +622,7 @@ public final class MediaManager {
|
|||||||
}
|
}
|
||||||
this.shareClientCount--;
|
this.shareClientCount--;
|
||||||
if(this.shareClientCount <= 0) {
|
if(this.shareClientCount <= 0) {
|
||||||
try {
|
this.stopCapture("次码流", this.videoCapturer);
|
||||||
this.videoCapturer.stopCapture();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e(MediaManager.class.getSimpleName(), "关闭视频捕获(次码流)异常", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -627,24 +630,18 @@ public final class MediaManager {
|
|||||||
public String photograph() {
|
public String photograph() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
String filepath;
|
String filepath;
|
||||||
|
final MediaVideoProperties mediaVideoProperties = this.mediaProperties.getVideos().get(this.videoQuantity);
|
||||||
|
final PhotographClient photographClient = new PhotographClient(this.imageQuantity, this.imagePath);
|
||||||
if(this.clientCount <= 0) {
|
if(this.clientCount <= 0) {
|
||||||
final MediaVideoProperties mediaVideoProperties = this.mediaProperties.getVideos().get(this.videoQuantity);
|
|
||||||
final PhotographClient photographClient = new PhotographClient(this.imageQuantity, this.imagePath);
|
|
||||||
filepath = photographClient.photograph(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), VideoSourceType.BACK, this.context);
|
filepath = photographClient.photograph(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), VideoSourceType.BACK, this.context);
|
||||||
return filepath;
|
} else if(this.recordClient != null) {
|
||||||
}
|
this.photographClient = photographClient;
|
||||||
this.photographClient = new PhotographClient(this.imageQuantity, this.imagePath);
|
|
||||||
if(this.recordClient == null) {
|
|
||||||
final MediaVideoProperties mediaVideoProperties = this.mediaProperties.getVideos().get(this.videoQuantity);
|
|
||||||
this.recordVideoCapturer.startCapture(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), mediaVideoProperties.getFrameRate());
|
|
||||||
filepath = this.photographClient.waitForPhotograph();
|
filepath = this.photographClient.waitForPhotograph();
|
||||||
try {
|
|
||||||
this.recordVideoCapturer.stopCapture();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e(MediaManager.class.getSimpleName(), "关闭视频捕获(主码流)异常", e);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
this.photographClient = photographClient;
|
||||||
|
this.recordVideoCapturer.startCapture(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), PhotographClient.CAPTURER_SIZE);
|
||||||
filepath = this.photographClient.waitForPhotograph();
|
filepath = this.photographClient.waitForPhotograph();
|
||||||
|
this.stopCapture("主码流", this.recordVideoCapturer);
|
||||||
}
|
}
|
||||||
this.photographClient = null;
|
this.photographClient = null;
|
||||||
return filepath;
|
return filepath;
|
||||||
@@ -663,9 +660,8 @@ public final class MediaManager {
|
|||||||
mediaVideoProperties.getBitrate(), mediaVideoProperties.getFrameRate(), this.iFrameInterval, mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(),
|
mediaVideoProperties.getBitrate(), mediaVideoProperties.getFrameRate(), this.iFrameInterval, mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(),
|
||||||
this.videoPath, this.taoyao, this.mainHandler
|
this.videoPath, this.taoyao, this.mainHandler
|
||||||
);
|
);
|
||||||
this.newClient(VideoSourceType.BACK);
|
|
||||||
this.recordVideoCapturer.startCapture(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), mediaVideoProperties.getFrameRate());
|
|
||||||
this.recordClient.start();
|
this.recordClient.start();
|
||||||
|
this.recordVideoCapturer.startCapture(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), mediaVideoProperties.getFrameRate());
|
||||||
return this.recordClient;
|
return this.recordClient;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -677,16 +673,22 @@ public final class MediaManager {
|
|||||||
} else {
|
} else {
|
||||||
this.recordClient.close();
|
this.recordClient.close();
|
||||||
this.recordClient = null;
|
this.recordClient = null;
|
||||||
try {
|
this.stopCapture("主码流", this.recordVideoCapturer);
|
||||||
this.recordVideoCapturer.stopCapture();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e(MediaManager.class.getSimpleName(), "关闭视频捕获(主码流)异常", e);
|
|
||||||
}
|
|
||||||
this.closeClient();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void stopCapture(String name, VideoCapturer videoCapturer) {
|
||||||
|
if(this.videoCapturer == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
videoCapturer.stopCapture();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.e(MediaManager.class.getSimpleName(), "关闭视频捕获异常:" + name, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param flag Config.WHAT_*
|
* @param flag Config.WHAT_*
|
||||||
* @param videoTrack 视频媒体流Track
|
* @param videoTrack 视频媒体流Track
|
||||||
@@ -703,12 +705,10 @@ public final class MediaManager {
|
|||||||
surfaceViewRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
|
surfaceViewRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
|
||||||
// 硬件拉伸
|
// 硬件拉伸
|
||||||
surfaceViewRenderer.setEnableHardwareScaler(true);
|
surfaceViewRenderer.setEnableHardwareScaler(true);
|
||||||
|
// surfaceViewRenderer.setFpsReduction();
|
||||||
// 加载OpenSL ES
|
// 加载OpenSL ES
|
||||||
surfaceViewRenderer.init(this.shareEglContext, null);
|
surfaceViewRenderer.init(this.shareEglContext, null);
|
||||||
// 强制播放
|
// 添加播放
|
||||||
if(!videoTrack.enabled()) {
|
|
||||||
videoTrack.setEnabled(true);
|
|
||||||
}
|
|
||||||
videoTrack.addSink(surfaceViewRenderer);
|
videoTrack.addSink(surfaceViewRenderer);
|
||||||
});
|
});
|
||||||
// 页面加载
|
// 页面加载
|
||||||
|
|||||||
@@ -62,10 +62,9 @@ public class LocalClient extends RoomClient {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ListUtils.getOnlyOne(this.mediaStream.videoTracks, videoTrack -> {
|
ListUtils.getOnlyOne(this.mediaStream.videoTracks, videoTrack -> {
|
||||||
|
videoTrack.setEnabled(true);
|
||||||
if(this.surfaceViewRenderer == null) {
|
if(this.surfaceViewRenderer == null) {
|
||||||
this.surfaceViewRenderer = this.mediaManager.buildSurfaceViewRenderer(Config.WHAT_NEW_LOCAL_VIDEO, videoTrack);
|
this.surfaceViewRenderer = this.mediaManager.buildSurfaceViewRenderer(Config.WHAT_NEW_LOCAL_VIDEO, videoTrack);
|
||||||
} else {
|
|
||||||
videoTrack.setEnabled(true);
|
|
||||||
}
|
}
|
||||||
return videoTrack;
|
return videoTrack;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.acgist.taoyao.media.client;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.ImageFormat;
|
import android.graphics.ImageFormat;
|
||||||
@@ -13,7 +14,6 @@ import android.hardware.camera2.CameraCharacteristics;
|
|||||||
import android.hardware.camera2.CameraDevice;
|
import android.hardware.camera2.CameraDevice;
|
||||||
import android.hardware.camera2.CameraManager;
|
import android.hardware.camera2.CameraManager;
|
||||||
import android.hardware.camera2.CaptureRequest;
|
import android.hardware.camera2.CaptureRequest;
|
||||||
import android.hardware.camera2.TotalCaptureResult;
|
|
||||||
import android.hardware.camera2.params.OutputConfiguration;
|
import android.hardware.camera2.params.OutputConfiguration;
|
||||||
import android.hardware.camera2.params.SessionConfiguration;
|
import android.hardware.camera2.params.SessionConfiguration;
|
||||||
import android.media.Image;
|
import android.media.Image;
|
||||||
@@ -36,7 +36,6 @@ import java.nio.ByteBuffer;
|
|||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拍照终端
|
* 拍照终端
|
||||||
@@ -45,6 +44,8 @@ import java.util.concurrent.TimeUnit;
|
|||||||
*/
|
*/
|
||||||
public class PhotographClient {
|
public class PhotographClient {
|
||||||
|
|
||||||
|
public static final int CAPTURER_SIZE = 1;
|
||||||
|
|
||||||
private final int quantity;
|
private final int quantity;
|
||||||
private final String filename;
|
private final String filename;
|
||||||
private final String filepath;
|
private final String filepath;
|
||||||
@@ -53,7 +54,6 @@ public class PhotographClient {
|
|||||||
private Surface surface;
|
private Surface surface;
|
||||||
private ImageReader imageReader;
|
private ImageReader imageReader;
|
||||||
private CameraDevice cameraDevice;
|
private CameraDevice cameraDevice;
|
||||||
private CameraManager cameraManager;
|
|
||||||
private CameraCaptureSession cameraCaptureSession;
|
private CameraCaptureSession cameraCaptureSession;
|
||||||
|
|
||||||
public PhotographClient(int quantity, String path) {
|
public PhotographClient(int quantity, String path) {
|
||||||
@@ -65,8 +65,6 @@ public class PhotographClient {
|
|||||||
Log.i(RecordClient.class.getSimpleName(), "拍摄照片文件:" + this.filepath);
|
Log.i(RecordClient.class.getSimpleName(), "拍摄照片文件:" + this.filepath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================ 拉流拍照 ================ //
|
|
||||||
|
|
||||||
public String photograph(VideoFrame videoFrame) {
|
public String photograph(VideoFrame videoFrame) {
|
||||||
if(this.wait) {
|
if(this.wait) {
|
||||||
this.wait = false;
|
this.wait = false;
|
||||||
@@ -153,17 +151,31 @@ public class PhotographClient {
|
|||||||
return new YuvImage(nv21, ImageFormat.NV21, width, height, null);
|
return new YuvImage(nv21, ImageFormat.NV21, width, height, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================ Camera2拍照 ================ //
|
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
public String photograph(int width, int height, VideoSourceType type, Context context) {
|
public String photograph(int width, int height, VideoSourceType type, Context context) {
|
||||||
this.cameraManager = context.getSystemService(CameraManager.class);
|
final CameraManager cameraManager = context.getSystemService(CameraManager.class);
|
||||||
this.imageReader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 1);
|
this.imageReader = ImageReader.newInstance(width, height, ImageFormat.JPEG, PhotographClient.CAPTURER_SIZE);
|
||||||
this.surface = this.imageReader.getSurface();
|
this.surface = this.imageReader.getSurface();
|
||||||
this.imageReader.setOnImageAvailableListener(this.imageAvailableListener, null);
|
this.imageReader.setOnImageAvailableListener(this.imageAvailableListener, null);
|
||||||
try {
|
try {
|
||||||
final String cameraId = String.valueOf(type == VideoSourceType.BACK ? CameraCharacteristics.LENS_FACING_BACK : CameraCharacteristics.LENS_FACING_FRONT);
|
String cameraId = null;
|
||||||
this.cameraManager.openCamera(cameraId, this.cameraDeviceStateCallback, null);
|
final String[] cameraIdList = cameraManager.getCameraIdList();
|
||||||
|
for (String id : cameraIdList) {
|
||||||
|
final CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(id);
|
||||||
|
if(cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK && type == VideoSourceType.BACK) {
|
||||||
|
cameraId = id;
|
||||||
|
break;
|
||||||
|
} else if(cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT && type == VideoSourceType.FRONT) {
|
||||||
|
cameraId = id;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(cameraId == null) {
|
||||||
|
PhotographClient.this.closeCamera();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
cameraManager.openCamera(cameraId, this.cameraDeviceStateCallback, null);
|
||||||
} catch (CameraAccessException e) {
|
} catch (CameraAccessException e) {
|
||||||
Log.e(PhotographClient.class.getSimpleName(), "拍照异常", e);
|
Log.e(PhotographClient.class.getSimpleName(), "拍照异常", e);
|
||||||
PhotographClient.this.closeCamera();
|
PhotographClient.this.closeCamera();
|
||||||
@@ -171,25 +183,22 @@ public class PhotographClient {
|
|||||||
return this.filepath;
|
return this.filepath;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImageReader.OnImageAvailableListener imageAvailableListener = new ImageReader.OnImageAvailableListener() {
|
private ImageReader.OnImageAvailableListener imageAvailableListener = (ImageReader imageReader) -> {
|
||||||
@Override
|
final Image image = imageReader.acquireLatestImage();
|
||||||
public void onImageAvailable(ImageReader imageReader) {
|
final Image.Plane[] planes = image.getPlanes();
|
||||||
final Image image = imageReader.acquireLatestImage();
|
final ByteBuffer byteBuffer = planes[0].getBuffer();
|
||||||
final ByteBuffer byteBuffer = image.getPlanes()[0].getBuffer();
|
final byte[] bytes = new byte[byteBuffer.remaining()];
|
||||||
final byte[] bytes = new byte[byteBuffer.remaining()];
|
byteBuffer.get(bytes);
|
||||||
byteBuffer.get(bytes);
|
final File file = new File(PhotographClient.this.filepath);
|
||||||
final File file = new File(PhotographClient.this.filepath);
|
try (
|
||||||
try (
|
final OutputStream output = new FileOutputStream(file);
|
||||||
final OutputStream output = new FileOutputStream(file);
|
) {
|
||||||
) {
|
output.write(bytes,0,bytes.length);
|
||||||
output.write(bytes,0,bytes.length);
|
} catch (IOException e) {
|
||||||
} catch (IOException e) {
|
Log.e(PhotographClient.class.getSimpleName(), "拍照异常", e);
|
||||||
Log.e(PhotographClient.class.getSimpleName(), "拍照异常", e);
|
} finally {
|
||||||
PhotographClient.this.closeCamera();
|
image.close();
|
||||||
} finally {
|
PhotographClient.this.closeCamera();
|
||||||
image.close();
|
|
||||||
PhotographClient.this.closeCamera();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.acgist.taoyao.media.client;
|
package com.acgist.taoyao.media.client;
|
||||||
|
|
||||||
|
import android.media.AudioFormat;
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaCodecInfo;
|
import android.media.MediaCodecInfo;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
@@ -152,6 +153,7 @@ public class RecordClient extends Client implements VideoSink, JavaAudioDeviceMo
|
|||||||
}
|
}
|
||||||
Log.i(RecordClient.class.getSimpleName(), "录制视频文件:" + this.filepath);
|
Log.i(RecordClient.class.getSimpleName(), "录制视频文件:" + this.filepath);
|
||||||
super.init();
|
super.init();
|
||||||
|
this.mediaManager.newClient(VideoSourceType.BACK);
|
||||||
if (
|
if (
|
||||||
this.audioThread == null || !this.audioThread.isAlive() ||
|
this.audioThread == null || !this.audioThread.isAlive() ||
|
||||||
this.videoThread == null || !this.videoThread.isAlive()
|
this.videoThread == null || !this.videoThread.isAlive()
|
||||||
@@ -173,8 +175,10 @@ public class RecordClient extends Client implements VideoSink, JavaAudioDeviceMo
|
|||||||
// audioFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, this.sampleRate);
|
// audioFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, this.sampleRate);
|
||||||
// audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, this.channelCount);
|
// audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, this.channelCount);
|
||||||
audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, this.audioBitRate);
|
audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, this.audioBitRate);
|
||||||
|
// audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, AudioFormat.ENCODING_PCM_16BIT);
|
||||||
audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
|
audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
|
||||||
// audioFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
|
// audioFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
|
||||||
|
// audioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 1024 * 8 * 8); // 设置缓冲大小
|
||||||
this.audioCodec = MediaCodec.createEncoderByType(audioType);
|
this.audioCodec = MediaCodec.createEncoderByType(audioType);
|
||||||
this.audioCodec.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
this.audioCodec.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -449,6 +453,7 @@ public class RecordClient extends Client implements VideoSink, JavaAudioDeviceMo
|
|||||||
file.delete();
|
file.delete();
|
||||||
}
|
}
|
||||||
this.notifyAll();
|
this.notifyAll();
|
||||||
|
this.mediaManager.closeClient();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,10 +54,9 @@ public class RemoteClient extends RoomClient {
|
|||||||
.map(v -> (VideoTrack) v)
|
.map(v -> (VideoTrack) v)
|
||||||
.collect(Collectors.toList()),
|
.collect(Collectors.toList()),
|
||||||
videoTrack -> {
|
videoTrack -> {
|
||||||
|
videoTrack.setEnabled(true);
|
||||||
if(this.surfaceViewRenderer == null) {
|
if(this.surfaceViewRenderer == null) {
|
||||||
this.surfaceViewRenderer = this.mediaManager.buildSurfaceViewRenderer(Config.WHAT_NEW_REMOTE_VIDEO, videoTrack);
|
this.surfaceViewRenderer = this.mediaManager.buildSurfaceViewRenderer(Config.WHAT_NEW_REMOTE_VIDEO, videoTrack);
|
||||||
} else {
|
|
||||||
videoTrack.setEnabled(true);
|
|
||||||
}
|
}
|
||||||
return videoTrack;
|
return videoTrack;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,10 +233,9 @@ public class SessionClient extends Client {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ListUtils.getOnlyOne(this.remoteMediaStream.videoTracks, videoTrack -> {
|
ListUtils.getOnlyOne(this.remoteMediaStream.videoTracks, videoTrack -> {
|
||||||
|
videoTrack.setEnabled(true);
|
||||||
if(this.surfaceViewRenderer == null) {
|
if(this.surfaceViewRenderer == null) {
|
||||||
this.surfaceViewRenderer = this.mediaManager.buildSurfaceViewRenderer(Config.WHAT_NEW_REMOTE_VIDEO, videoTrack);
|
this.surfaceViewRenderer = this.mediaManager.buildSurfaceViewRenderer(Config.WHAT_NEW_REMOTE_VIDEO, videoTrack);
|
||||||
} else {
|
|
||||||
videoTrack.setEnabled(true);
|
|
||||||
}
|
}
|
||||||
return videoTrack;
|
return videoTrack;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -69,3 +69,8 @@ make -C worker
|
|||||||
* [Kurento](https://github.com/Kurento/kurento-media-server)
|
* [Kurento](https://github.com/Kurento/kurento-media-server)
|
||||||
* [Medooze](https://github.com/medooze/media-server)
|
* [Medooze](https://github.com/medooze/media-server)
|
||||||
* [Mediasoup](https://github.com/versatica/mediasoup)
|
* [Mediasoup](https://github.com/versatica/mediasoup)
|
||||||
|
|
||||||
|
## 协议
|
||||||
|
|
||||||
|
* https://www.ortc.org
|
||||||
|
* https://www.webrtc.org
|
||||||
|
|||||||
@@ -12,8 +12,3 @@
|
|||||||
## 信令格式
|
## 信令格式
|
||||||
|
|
||||||
[信令格式](https://localhost:8888/protocol/list)
|
[信令格式](https://localhost:8888/protocol/list)
|
||||||
|
|
||||||
## 协议
|
|
||||||
|
|
||||||
* https://www.ortc.org
|
|
||||||
* https://www.webrtc.org
|
|
||||||
|
|||||||
Reference in New Issue
Block a user