[*] 复用
This commit is contained in:
@@ -9,7 +9,6 @@ import android.media.projection.MediaProjectionManager;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.Looper;
|
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -187,9 +186,9 @@ public class MainActivity extends AppCompatActivity implements Serializable {
|
|||||||
private void record(View view) {
|
private void record(View view) {
|
||||||
final MediaManager mediaManager = MediaManager.getInstance();
|
final MediaManager mediaManager = MediaManager.getInstance();
|
||||||
if (mediaManager.isRecording()) {
|
if (mediaManager.isRecording()) {
|
||||||
mediaManager.stopRecordVideoCapture();
|
mediaManager.stopRecord();
|
||||||
} else {
|
} else {
|
||||||
mediaManager.startRecordVideoCapture();
|
mediaManager.startRecord();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ 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;
|
||||||
@@ -24,6 +23,7 @@ import org.webrtc.AudioTrack;
|
|||||||
import org.webrtc.Camera2Enumerator;
|
import org.webrtc.Camera2Enumerator;
|
||||||
import org.webrtc.CameraEnumerator;
|
import org.webrtc.CameraEnumerator;
|
||||||
import org.webrtc.CameraVideoCapturer;
|
import org.webrtc.CameraVideoCapturer;
|
||||||
|
import org.webrtc.CapturerObserver;
|
||||||
import org.webrtc.DefaultVideoDecoderFactory;
|
import org.webrtc.DefaultVideoDecoderFactory;
|
||||||
import org.webrtc.DefaultVideoEncoderFactory;
|
import org.webrtc.DefaultVideoEncoderFactory;
|
||||||
import org.webrtc.EglBase;
|
import org.webrtc.EglBase;
|
||||||
@@ -37,6 +37,7 @@ import org.webrtc.SurfaceViewRenderer;
|
|||||||
import org.webrtc.VideoCapturer;
|
import org.webrtc.VideoCapturer;
|
||||||
import org.webrtc.VideoDecoderFactory;
|
import org.webrtc.VideoDecoderFactory;
|
||||||
import org.webrtc.VideoEncoderFactory;
|
import org.webrtc.VideoEncoderFactory;
|
||||||
|
import org.webrtc.VideoFrame;
|
||||||
import org.webrtc.VideoSource;
|
import org.webrtc.VideoSource;
|
||||||
import org.webrtc.VideoTrack;
|
import org.webrtc.VideoTrack;
|
||||||
import org.webrtc.audio.JavaAudioDeviceModule;
|
import org.webrtc.audio.JavaAudioDeviceModule;
|
||||||
@@ -46,8 +47,6 @@ import java.util.Arrays;
|
|||||||
/**
|
/**
|
||||||
* 媒体来源管理器
|
* 媒体来源管理器
|
||||||
*
|
*
|
||||||
* 注意:镜头选择可以使用代码实现,如果可以经理直接进行物理旋转。
|
|
||||||
*
|
|
||||||
* @author acgist
|
* @author acgist
|
||||||
*
|
*
|
||||||
* TODO:动态码率(BITRATE_MODE_VBR、BITRATE_MODE)
|
* TODO:动态码率(BITRATE_MODE_VBR、BITRATE_MODE)
|
||||||
@@ -64,10 +63,6 @@ public final class MediaManager {
|
|||||||
* 当前终端数量
|
* 当前终端数量
|
||||||
*/
|
*/
|
||||||
private volatile int clientCount;
|
private volatile int clientCount;
|
||||||
/**
|
|
||||||
* 当前媒体共享数量
|
|
||||||
*/
|
|
||||||
private volatile int shareClientCount;
|
|
||||||
/**
|
/**
|
||||||
* 视频路径
|
* 视频路径
|
||||||
*/
|
*/
|
||||||
@@ -121,13 +116,13 @@ public final class MediaManager {
|
|||||||
*/
|
*/
|
||||||
private MediaProperties mediaProperties;
|
private MediaProperties mediaProperties;
|
||||||
/**
|
/**
|
||||||
* 音频配置
|
* 当前共享视频配置
|
||||||
*/
|
|
||||||
private MediaAudioProperties mediaAudioProperties;
|
|
||||||
/**
|
|
||||||
* 视频配置
|
|
||||||
*/
|
*/
|
||||||
private MediaVideoProperties mediaVideoProperties;
|
private MediaVideoProperties mediaVideoProperties;
|
||||||
|
/**
|
||||||
|
* 当前共享音频配置
|
||||||
|
*/
|
||||||
|
private MediaAudioProperties mediaAudioProperties;
|
||||||
/**
|
/**
|
||||||
* WebRTC配置
|
* WebRTC配置
|
||||||
*/
|
*/
|
||||||
@@ -152,34 +147,30 @@ public final class MediaManager {
|
|||||||
* 音频来源
|
* 音频来源
|
||||||
*/
|
*/
|
||||||
private AudioSource audioSource;
|
private AudioSource audioSource;
|
||||||
/**
|
|
||||||
* 视频Track
|
|
||||||
*/
|
|
||||||
private VideoTrack videoTrack;
|
|
||||||
/**
|
|
||||||
* 视频来源
|
|
||||||
*/
|
|
||||||
private VideoSource videoSource;
|
|
||||||
/**
|
/**
|
||||||
* 视频捕获
|
* 视频捕获
|
||||||
*/
|
*/
|
||||||
private VideoCapturer videoCapturer;
|
private VideoCapturer videoCapturer;
|
||||||
|
/**
|
||||||
|
* 主码流视频Track
|
||||||
|
*/
|
||||||
|
private VideoTrack mainVideoTrack;
|
||||||
|
/**
|
||||||
|
* 主码流视频来源
|
||||||
|
*/
|
||||||
|
private VideoSource mainVideoSource;
|
||||||
|
/**
|
||||||
|
* 次码流视频Track
|
||||||
|
*/
|
||||||
|
private VideoTrack shareVideoTrack;
|
||||||
|
/**
|
||||||
|
* 次码流视频来源
|
||||||
|
*/
|
||||||
|
private VideoSource shareVideoSource;
|
||||||
/**
|
/**
|
||||||
* 录制终端
|
* 录制终端
|
||||||
*/
|
*/
|
||||||
private RecordClient recordClient;
|
private RecordClient recordClient;
|
||||||
/**
|
|
||||||
* 录制视频Track
|
|
||||||
*/
|
|
||||||
private VideoTrack recordVideoTrack;
|
|
||||||
/**
|
|
||||||
* 录制视频来源
|
|
||||||
*/
|
|
||||||
private VideoSource recordVideoSource;
|
|
||||||
/**
|
|
||||||
* 录制视频捕获
|
|
||||||
*/
|
|
||||||
private VideoCapturer recordVideoCapturer;
|
|
||||||
/**
|
/**
|
||||||
* 拍照终端
|
* 拍照终端
|
||||||
*/
|
*/
|
||||||
@@ -223,7 +214,6 @@ public final class MediaManager {
|
|||||||
|
|
||||||
private MediaManager() {
|
private MediaManager() {
|
||||||
this.clientCount = 0;
|
this.clientCount = 0;
|
||||||
this.shareClientCount = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -284,9 +274,11 @@ public final class MediaManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.clientCount <= 0) {
|
if (this.clientCount <= 0) {
|
||||||
|
Log.i(MediaManager.class.getSimpleName(), "加载PeerConnectionFactory");
|
||||||
this.initPeerConnectionFactory();
|
this.initPeerConnectionFactory();
|
||||||
this.initMedia(videoSourceType);
|
|
||||||
this.nativeInit();
|
this.nativeInit();
|
||||||
|
this.initMedia(videoSourceType);
|
||||||
|
this.startVideoCapture();
|
||||||
}
|
}
|
||||||
this.clientCount++;
|
this.clientCount++;
|
||||||
}
|
}
|
||||||
@@ -296,7 +288,6 @@ public final class MediaManager {
|
|||||||
/**
|
/**
|
||||||
* 关闭一个终端
|
* 关闭一个终端
|
||||||
* 最后一个终端关闭时,释放所有资源。
|
* 最后一个终端关闭时,释放所有资源。
|
||||||
* 注意:所有本地媒体关闭调用,不要直接关闭本地媒体流。
|
|
||||||
*
|
*
|
||||||
* @return 剩余终端数量
|
* @return 剩余终端数量
|
||||||
*/
|
*/
|
||||||
@@ -304,7 +295,12 @@ public final class MediaManager {
|
|||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
this.clientCount--;
|
this.clientCount--;
|
||||||
if (this.clientCount <= 0) {
|
if (this.clientCount <= 0) {
|
||||||
this.close();
|
Log.i(MediaManager.class.getSimpleName(), "释放PeerConnectionFactory");
|
||||||
|
this.closeAudio();
|
||||||
|
this.closeMainVideo();
|
||||||
|
this.closeShareVideo();
|
||||||
|
this.closeMedia();
|
||||||
|
this.stopVideoCapture();
|
||||||
this.nativeStop();
|
this.nativeStop();
|
||||||
this.stopPeerConnectionFactory();
|
this.stopPeerConnectionFactory();
|
||||||
}
|
}
|
||||||
@@ -380,7 +376,7 @@ public final class MediaManager {
|
|||||||
// .setUseLowLatency()
|
// .setUseLowLatency()
|
||||||
.setSamplesReadyCallback(audioSamples -> {
|
.setSamplesReadyCallback(audioSamples -> {
|
||||||
if(this.recordClient != null) {
|
if(this.recordClient != null) {
|
||||||
this.recordClient.putAudio(audioSamples);
|
this.recordClient.onWebRtcAudioRecordSamplesReady(audioSamples);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.setAudioTrackStateCallback(new JavaAudioDeviceModule.AudioTrackStateCallback() {
|
.setAudioTrackStateCallback(new JavaAudioDeviceModule.AudioTrackStateCallback() {
|
||||||
@@ -468,10 +464,8 @@ public final class MediaManager {
|
|||||||
for (String name : names) {
|
for (String name : names) {
|
||||||
if (this.videoSourceType == VideoSourceType.FRONT && cameraEnumerator.isFrontFacing(name)) {
|
if (this.videoSourceType == VideoSourceType.FRONT && cameraEnumerator.isFrontFacing(name)) {
|
||||||
this.videoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler());
|
this.videoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler());
|
||||||
this.recordVideoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler());
|
|
||||||
} else if (this.videoSourceType == VideoSourceType.BACK && cameraEnumerator.isBackFacing(name)) {
|
} else if (this.videoSourceType == VideoSourceType.BACK && cameraEnumerator.isBackFacing(name)) {
|
||||||
this.videoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler());
|
this.videoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler());
|
||||||
this.recordVideoCapturer = cameraEnumerator.createCapturer(name, new MediaCameraEventsHandler());
|
|
||||||
} else {
|
} else {
|
||||||
// 忽略其他摄像头
|
// 忽略其他摄像头
|
||||||
}
|
}
|
||||||
@@ -492,7 +486,6 @@ public final class MediaManager {
|
|||||||
*/
|
*/
|
||||||
public void initScreen(Intent intent) {
|
public void initScreen(Intent intent) {
|
||||||
this.videoCapturer = new ScreenCapturerAndroid(intent, new ScreenCallback());
|
this.videoCapturer = new ScreenCapturerAndroid(intent, new ScreenCallback());
|
||||||
this.recordVideoCapturer = new ScreenCapturerAndroid(intent, new ScreenCallback());
|
|
||||||
this.initVideoTrack();
|
this.initVideoTrack();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,33 +497,23 @@ public final class MediaManager {
|
|||||||
this.surfaceTextureHelper = SurfaceTextureHelper.create("MediaVideoThread", this.shareEglContext);
|
this.surfaceTextureHelper = SurfaceTextureHelper.create("MediaVideoThread", this.shareEglContext);
|
||||||
// this.surfaceTextureHelper.setTextureSize();
|
// this.surfaceTextureHelper.setTextureSize();
|
||||||
// this.surfaceTextureHelper.setFrameRotation();
|
// this.surfaceTextureHelper.setFrameRotation();
|
||||||
// 次码流
|
|
||||||
this.videoSource = this.peerConnectionFactory.createVideoSource(this.videoCapturer.isScreencast());
|
|
||||||
this.videoCapturer.initialize(this.surfaceTextureHelper, this.context, this.videoSource.getCapturerObserver());
|
|
||||||
this.videoTrack = this.peerConnectionFactory.createVideoTrack("TaoyaoV0", this.videoSource);
|
|
||||||
this.videoTrack.setEnabled(true);
|
|
||||||
this.mediaStream.addTrack(this.videoTrack);
|
|
||||||
Log.i(MediaManager.class.getSimpleName(), "加载视频(次码流):" + this.videoTrack.id());
|
|
||||||
// 主码流
|
// 主码流
|
||||||
this.recordVideoSource = this.peerConnectionFactory.createVideoSource(this.recordVideoCapturer.isScreencast());
|
this.mainVideoSource = this.peerConnectionFactory.createVideoSource(this.videoCapturer.isScreencast());
|
||||||
this.recordVideoCapturer.initialize(this.surfaceTextureHelper, this.context, this.recordVideoSource.getCapturerObserver());
|
this.mainVideoTrack = this.peerConnectionFactory.createVideoTrack("TaoyaoV0", this.mainVideoSource);
|
||||||
this.recordVideoTrack = this.peerConnectionFactory.createVideoTrack("TaoyaoV1", this.recordVideoSource);
|
this.mainVideoTrack.setEnabled(true);
|
||||||
this.recordVideoTrack.addSink(videoFrame -> {
|
Log.i(MediaManager.class.getSimpleName(), "加载视频(主码流):" + this.mainVideoTrack.id());
|
||||||
// 录制
|
// 次码流
|
||||||
if (this.recordClient != null) {
|
this.shareVideoSource = this.peerConnectionFactory.createVideoSource(this.videoCapturer.isScreencast());
|
||||||
videoFrame.retain();
|
this.shareVideoSource.adaptOutputFormat(this.mediaVideoProperties.getWidth(), this.mediaVideoProperties.getHeight(), this.mediaVideoProperties.getFrameRate());
|
||||||
this.recordClient.putVideo(videoFrame);
|
this.shareVideoTrack = this.peerConnectionFactory.createVideoTrack("TaoyaoV1", this.shareVideoSource);
|
||||||
}
|
this.shareVideoTrack.setEnabled(true);
|
||||||
// 拍照
|
Log.i(MediaManager.class.getSimpleName(), "加载视频(次码流):" + this.shareVideoTrack.id());
|
||||||
if (this.photographClient != null) {
|
// 共享次码流
|
||||||
videoFrame.retain();
|
this.mediaStream.addTrack(this.shareVideoTrack);
|
||||||
this.photographClient.photograph(videoFrame);
|
// 视频捕获
|
||||||
}
|
this.videoCapturer.initialize(this.surfaceTextureHelper, this.context, new VideoCapturerObserver());
|
||||||
});
|
// 次码流视频处理
|
||||||
this.recordVideoTrack.setEnabled(true);
|
// this.shareVideoSource.setVideoProcessor();
|
||||||
Log.i(MediaManager.class.getSimpleName(), "加载视频(主码流):" + this.recordVideoTrack.id());
|
|
||||||
// 视频处理
|
|
||||||
// this.videoSource.setVideoProcessor();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -542,8 +525,8 @@ public final class MediaManager {
|
|||||||
*/
|
*/
|
||||||
public void updateMediaConfig(MediaProperties mediaProperties, MediaAudioProperties mediaAudioProperties, MediaVideoProperties mediaVideoProperties) {
|
public void updateMediaConfig(MediaProperties mediaProperties, MediaAudioProperties mediaAudioProperties, MediaVideoProperties mediaVideoProperties) {
|
||||||
this.mediaProperties = mediaProperties;
|
this.mediaProperties = mediaProperties;
|
||||||
this.updateAudioConfig(mediaAudioProperties);
|
this.updateAudioConfig(this.mediaProperties.getAudio());
|
||||||
this.updateVideoConfig(mediaVideoProperties);
|
this.updateVideoConfig(this.mediaProperties.getVideo());
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
this.notifyAll();
|
this.notifyAll();
|
||||||
}
|
}
|
||||||
@@ -555,10 +538,10 @@ 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.shareVideoSource == null) {
|
||||||
this.stopCapture("次码流", this.videoCapturer);
|
return;
|
||||||
this.videoCapturer.startCapture(this.mediaVideoProperties.getWidth(), this.mediaVideoProperties.getHeight(), this.mediaVideoProperties.getFrameRate());
|
|
||||||
}
|
}
|
||||||
|
this.shareVideoSource.adaptOutputFormat(this.mediaVideoProperties.getWidth(), this.mediaVideoProperties.getHeight(), this.mediaVideoProperties.getFrameRate());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateWebrtcConfig(WebrtcProperties webrtcProperties) {
|
public void updateWebrtcConfig(WebrtcProperties webrtcProperties) {
|
||||||
@@ -597,33 +580,22 @@ public final class MediaManager {
|
|||||||
return this.mediaStream;
|
return this.mediaStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startVideoCapture() {
|
private void startVideoCapture() {
|
||||||
synchronized (this) {
|
|
||||||
if(this.videoCapturer == null) {
|
if(this.videoCapturer == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(this.shareClientCount > 0) {
|
final MediaVideoProperties mediaVideoProperties = this.mediaProperties.getVideos().get(this.videoQuantity);
|
||||||
this.shareClientCount++;
|
this.videoCapturer.startCapture(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), mediaVideoProperties.getFrameRate());
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
this.shareClientCount++;
|
|
||||||
this.videoCapturer.startCapture(this.mediaVideoProperties.getWidth(), this.mediaVideoProperties.getHeight(), this.mediaVideoProperties.getFrameRate());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stopVideoCapture() {
|
private void stopVideoCapture() {
|
||||||
synchronized (this) {
|
|
||||||
if(this.videoCapturer == null) {
|
if(this.videoCapturer == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(this.shareClientCount <= 0) {
|
try {
|
||||||
return;
|
videoCapturer.stopCapture();
|
||||||
}
|
} catch (InterruptedException e) {
|
||||||
this.shareClientCount--;
|
Log.e(MediaManager.class.getSimpleName(), "关闭视频捕获异常", e);
|
||||||
if(this.shareClientCount <= 0) {
|
|
||||||
this.stopCapture("次码流", this.videoCapturer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,21 +606,18 @@ public final class MediaManager {
|
|||||||
final PhotographClient photographClient = new PhotographClient(this.imageQuantity, this.imagePath);
|
final PhotographClient photographClient = new PhotographClient(this.imageQuantity, this.imagePath);
|
||||||
if(this.clientCount <= 0) {
|
if(this.clientCount <= 0) {
|
||||||
filepath = photographClient.photograph(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), VideoSourceType.BACK, this.context);
|
filepath = photographClient.photograph(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), VideoSourceType.BACK, this.context);
|
||||||
} else if(this.recordClient != null) {
|
|
||||||
this.photographClient = photographClient;
|
|
||||||
filepath = this.photographClient.waitForPhotograph();
|
|
||||||
} else {
|
} else {
|
||||||
this.photographClient = photographClient;
|
this.photographClient = photographClient;
|
||||||
this.recordVideoCapturer.startCapture(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), PhotographClient.CAPTURER_SIZE);
|
this.mainVideoTrack.addSink(this.photographClient);
|
||||||
filepath = this.photographClient.waitForPhotograph();
|
filepath = this.photographClient.waitForPhotograph();
|
||||||
this.stopCapture("主码流", this.recordVideoCapturer);
|
this.mainVideoTrack.removeSink(this.photographClient);
|
||||||
}
|
}
|
||||||
this.photographClient = null;
|
this.photographClient = null;
|
||||||
return filepath;
|
return filepath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public RecordClient startRecordVideoCapture() {
|
public RecordClient startRecord() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if(this.recordClient != null) {
|
if(this.recordClient != null) {
|
||||||
return this.recordClient;
|
return this.recordClient;
|
||||||
@@ -657,38 +626,28 @@ public final class MediaManager {
|
|||||||
final MediaVideoProperties mediaVideoProperties = this.mediaProperties.getVideos().get(this.videoQuantity);
|
final MediaVideoProperties mediaVideoProperties = this.mediaProperties.getVideos().get(this.videoQuantity);
|
||||||
this.recordClient = new RecordClient(
|
this.recordClient = new RecordClient(
|
||||||
mediaAudioProperties.getBitrate(), mediaAudioProperties.getSampleRate(), this.channelCount,
|
mediaAudioProperties.getBitrate(), mediaAudioProperties.getSampleRate(), this.channelCount,
|
||||||
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.recordClient.start();
|
this.recordClient.start();
|
||||||
this.recordVideoCapturer.startCapture(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), mediaVideoProperties.getFrameRate());
|
this.mainVideoTrack.addSink(this.recordClient);
|
||||||
return this.recordClient;
|
return this.recordClient;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stopRecordVideoCapture() {
|
public void stopRecord() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if(this.recordClient == null) {
|
if(this.recordClient == null) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
this.mainVideoTrack.removeSink(this.recordClient);
|
||||||
this.recordClient.close();
|
this.recordClient.close();
|
||||||
this.recordClient = null;
|
this.recordClient = null;
|
||||||
this.stopCapture("主码流", this.recordVideoCapturer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
@@ -736,33 +695,25 @@ public final class MediaManager {
|
|||||||
/**
|
/**
|
||||||
* 关闭视频
|
* 关闭视频
|
||||||
*/
|
*/
|
||||||
private void closeVideo() {
|
private void closeShareVideo() {
|
||||||
// if(this.videoTrack != null) {
|
// if(this.videoTrack != null) {
|
||||||
// this.videoTrack.dispose();
|
// this.videoTrack.dispose();
|
||||||
// this.videoTrack = null;
|
// this.videoTrack = null;
|
||||||
// }
|
// }
|
||||||
if(this.videoSource != null) {
|
if(this.shareVideoSource != null) {
|
||||||
this.videoSource.dispose();
|
this.shareVideoSource.dispose();
|
||||||
this.videoSource = null;
|
this.shareVideoSource = null;
|
||||||
}
|
|
||||||
if (this.videoCapturer != null) {
|
|
||||||
this.videoCapturer.dispose();
|
|
||||||
this.videoCapturer = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closeRecord() {
|
private void closeMainVideo() {
|
||||||
if(this.recordVideoTrack != null) {
|
if(this.mainVideoTrack != null) {
|
||||||
this.recordVideoTrack.dispose();
|
this.mainVideoTrack.dispose();
|
||||||
this.recordVideoTrack = null;
|
this.mainVideoTrack = null;
|
||||||
}
|
}
|
||||||
if(this.recordVideoSource != null) {
|
if(this.mainVideoSource != null) {
|
||||||
this.recordVideoSource.dispose();
|
this.mainVideoSource.dispose();
|
||||||
this.recordVideoSource = null;
|
this.mainVideoSource = null;
|
||||||
}
|
|
||||||
if(this.recordVideoCapturer != null) {
|
|
||||||
this.recordVideoCapturer.dispose();
|
|
||||||
this.recordVideoCapturer = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -776,6 +727,10 @@ public final class MediaManager {
|
|||||||
this.mediaStream.dispose();
|
this.mediaStream.dispose();
|
||||||
this.mediaStream = null;
|
this.mediaStream = null;
|
||||||
}
|
}
|
||||||
|
if (this.videoCapturer != null) {
|
||||||
|
this.videoCapturer.dispose();
|
||||||
|
this.videoCapturer = null;
|
||||||
|
}
|
||||||
if(this.surfaceTextureHelper != null) {
|
if(this.surfaceTextureHelper != null) {
|
||||||
this.surfaceTextureHelper.dispose();
|
this.surfaceTextureHelper.dispose();
|
||||||
this.surfaceTextureHelper = null;
|
this.surfaceTextureHelper = null;
|
||||||
@@ -786,14 +741,34 @@ public final class MediaManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private class VideoCapturerObserver implements CapturerObserver {
|
||||||
* 释放资源
|
|
||||||
*/
|
private CapturerObserver mainObserver;
|
||||||
private void close() {
|
private CapturerObserver shareObserver;
|
||||||
this.closeAudio();
|
|
||||||
this.closeVideo();
|
public VideoCapturerObserver() {
|
||||||
this.closeRecord();
|
this.mainObserver = MediaManager.this.mainVideoSource.getCapturerObserver();
|
||||||
this.closeMedia();
|
this.shareObserver = MediaManager.this.shareVideoSource.getCapturerObserver();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCapturerStarted(boolean status) {
|
||||||
|
this.mainObserver.onCapturerStarted(status);
|
||||||
|
this.shareObserver.onCapturerStarted(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCapturerStopped() {
|
||||||
|
this.mainObserver.onCapturerStopped();
|
||||||
|
this.shareObserver.onCapturerStopped();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFrameCaptured(VideoFrame videoFrame) {
|
||||||
|
this.mainObserver.onFrameCaptured(videoFrame);
|
||||||
|
this.shareObserver.onFrameCaptured(videoFrame);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -23,9 +23,11 @@ import android.util.Log;
|
|||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
|
|
||||||
import com.acgist.taoyao.boot.utils.DateUtils;
|
import com.acgist.taoyao.boot.utils.DateUtils;
|
||||||
|
import com.acgist.taoyao.media.MediaManager;
|
||||||
import com.acgist.taoyao.media.VideoSourceType;
|
import com.acgist.taoyao.media.VideoSourceType;
|
||||||
|
|
||||||
import org.webrtc.VideoFrame;
|
import org.webrtc.VideoFrame;
|
||||||
|
import org.webrtc.VideoSink;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -42,7 +44,7 @@ import java.util.List;
|
|||||||
*
|
*
|
||||||
* @author acgist
|
* @author acgist
|
||||||
*/
|
*/
|
||||||
public class PhotographClient {
|
public class PhotographClient implements VideoSink {
|
||||||
|
|
||||||
public static final int CAPTURER_SIZE = 1;
|
public static final int CAPTURER_SIZE = 1;
|
||||||
|
|
||||||
@@ -65,6 +67,12 @@ public class PhotographClient {
|
|||||||
Log.i(RecordClient.class.getSimpleName(), "拍摄照片文件:" + this.filepath);
|
Log.i(RecordClient.class.getSimpleName(), "拍摄照片文件:" + this.filepath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFrame(VideoFrame videoFrame) {
|
||||||
|
videoFrame.retain();
|
||||||
|
this.photograph(videoFrame);
|
||||||
|
}
|
||||||
|
|
||||||
public String photograph(VideoFrame videoFrame) {
|
public String photograph(VideoFrame videoFrame) {
|
||||||
if(this.wait) {
|
if(this.wait) {
|
||||||
this.wait = false;
|
this.wait = false;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.media.MediaMuxer;
|
|||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
|
import android.provider.MediaStore;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.acgist.taoyao.boot.utils.DateUtils;
|
import com.acgist.taoyao.boot.utils.DateUtils;
|
||||||
@@ -117,14 +118,6 @@ public class RecordClient extends Client implements VideoSink, JavaAudioDeviceMo
|
|||||||
* 媒体合成器
|
* 媒体合成器
|
||||||
*/
|
*/
|
||||||
private MediaMuxer mediaMuxer;
|
private MediaMuxer mediaMuxer;
|
||||||
/**
|
|
||||||
* 音频队列
|
|
||||||
*/
|
|
||||||
private final BlockingQueue<JavaAudioDeviceModule.AudioSamples> audioSamplesQueue;
|
|
||||||
/**
|
|
||||||
* 视频队列
|
|
||||||
*/
|
|
||||||
private final BlockingQueue<VideoFrame> videoFrameQueue;
|
|
||||||
|
|
||||||
public RecordClient(
|
public RecordClient(
|
||||||
int audioBitRate, int sampleRate, int channelCount,
|
int audioBitRate, int sampleRate, int channelCount,
|
||||||
@@ -142,8 +135,6 @@ public class RecordClient extends Client implements VideoSink, JavaAudioDeviceMo
|
|||||||
this.height = height;
|
this.height = height;
|
||||||
this.filename = DateUtils.format(LocalDateTime.now(), DateUtils.DateTimeStyle.YYYYMMDDHH24MMSS) + ".mp4";
|
this.filename = DateUtils.format(LocalDateTime.now(), DateUtils.DateTimeStyle.YYYYMMDDHH24MMSS) + ".mp4";
|
||||||
this.filepath = Paths.get(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath(), path, this.filename).toString();
|
this.filepath = Paths.get(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath(), path, this.filename).toString();
|
||||||
this.audioSamplesQueue = new LinkedBlockingQueue<>();
|
|
||||||
this.videoFrameQueue = new LinkedBlockingQueue<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
@@ -201,26 +192,6 @@ public class RecordClient extends Client implements VideoSink, JavaAudioDeviceMo
|
|||||||
JavaAudioDeviceModule.AudioSamples audioSamples = null;
|
JavaAudioDeviceModule.AudioSamples audioSamples = null;
|
||||||
final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||||
while (!this.close) {
|
while (!this.close) {
|
||||||
try {
|
|
||||||
audioSamples = this.audioSamplesQueue.poll(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e(RecordClient.class.getSimpleName(), "录制线程等待异常", e);
|
|
||||||
}
|
|
||||||
if(audioSamples == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int index = this.audioCodec.dequeueInputBuffer(WAIT_TIME_US);
|
|
||||||
if (index >= 0) {
|
|
||||||
final byte[] data = audioSamples.getData();
|
|
||||||
final ByteBuffer buffer = this.audioCodec.getInputBuffer(index);
|
|
||||||
buffer.put(data);
|
|
||||||
this.audioCodec.queueInputBuffer(index, 0, data.length, this.audioPts, 0);
|
|
||||||
// 1000000 microseconds / 48000 hz / 2 bytes
|
|
||||||
this.audioPts += data.length * (1_000_000 / audioSamples.getSampleRate() / 2);
|
|
||||||
} else {
|
|
||||||
// WARN
|
|
||||||
}
|
|
||||||
audioSamples = null;
|
|
||||||
outputIndex = this.audioCodec.dequeueOutputBuffer(bufferInfo, WAIT_TIME_US);
|
outputIndex = this.audioCodec.dequeueOutputBuffer(bufferInfo, WAIT_TIME_US);
|
||||||
if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
|
if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
|
||||||
// } else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
|
// } else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
|
||||||
@@ -281,21 +252,6 @@ public class RecordClient extends Client implements VideoSink, JavaAudioDeviceMo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param audioSamples PCM数据
|
|
||||||
*/
|
|
||||||
public void putAudio(JavaAudioDeviceModule.AudioSamples audioSamples) {
|
|
||||||
if(this.close || !this.audioActive) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Log.i(RecordClient.class.getSimpleName(), "音频信息:" + audioSamples.getAudioFormat());
|
|
||||||
try {
|
|
||||||
this.audioSamplesQueue.put(audioSamples);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e(RecordClient.class.getSimpleName(), "录制线程等待异常", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param videoType 视频格式
|
* @param videoType 视频格式
|
||||||
*/
|
*/
|
||||||
@@ -330,7 +286,7 @@ public class RecordClient extends Client implements VideoSink, JavaAudioDeviceMo
|
|||||||
final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||||
while (!this.close) {
|
while (!this.close) {
|
||||||
try {
|
try {
|
||||||
videoFrame = this.videoFrameQueue.poll(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
|
videoFrame = this.vq.poll(WAIT_TIME_MS, TimeUnit.MILLISECONDS);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Log.e(RecordClient.class.getSimpleName(), "录制线程等待异常", e);
|
Log.e(RecordClient.class.getSimpleName(), "录制线程等待异常", e);
|
||||||
}
|
}
|
||||||
@@ -408,18 +364,6 @@ public class RecordClient extends Client implements VideoSink, JavaAudioDeviceMo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void putVideo(VideoFrame videoFrame) {
|
|
||||||
if (this.close || !this.videoActive) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Log.i(RecordClient.class.getSimpleName(), "视频信息:" + videoFrame.getRotatedWidth() + " - " + videoFrame.getRotatedHeight());
|
|
||||||
try {
|
|
||||||
this.videoFrameQueue.put(videoFrame);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Log.e(RecordClient.class.getSimpleName(), "录制线程等待异常", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initMediaMuxer() {
|
private void initMediaMuxer() {
|
||||||
try {
|
try {
|
||||||
this.mediaMuxer = new MediaMuxer(
|
this.mediaMuxer = new MediaMuxer(
|
||||||
@@ -465,12 +409,57 @@ public class RecordClient extends Client implements VideoSink, JavaAudioDeviceMo
|
|||||||
return this.filepath;
|
return this.filepath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void onFrame(VideoFrame videoFrame) {
|
* @param audioSamples PCM数据
|
||||||
}
|
*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWebRtcAudioRecordSamplesReady(JavaAudioDeviceModule.AudioSamples audioSamples) {
|
public void onWebRtcAudioRecordSamplesReady(JavaAudioDeviceModule.AudioSamples audioSamples) {
|
||||||
|
if(this.close || !this.audioActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log.i(RecordClient.class.getSimpleName(), "音频信息:" + audioSamples.getAudioFormat());
|
||||||
|
int index = this.audioCodec.dequeueInputBuffer(WAIT_TIME_US);
|
||||||
|
if (index >= 0) {
|
||||||
|
final byte[] data = audioSamples.getData();
|
||||||
|
final ByteBuffer buffer = this.audioCodec.getInputBuffer(index);
|
||||||
|
buffer.put(data);
|
||||||
|
this.audioCodec.queueInputBuffer(index, 0, data.length, this.audioPts, 0);
|
||||||
|
// 1000000 microseconds / 48000 hz / 2 bytes
|
||||||
|
this.audioPts += data.length * (1_000_000 / audioSamples.getSampleRate() / 2);
|
||||||
|
} else {
|
||||||
|
// WARN
|
||||||
|
}
|
||||||
|
audioSamples = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFrame(VideoFrame videoFrame) {
|
||||||
|
videoFrame.retain();
|
||||||
|
if (this.close || !this.videoActive) {
|
||||||
|
videoFrame.release();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log.i(RecordClient.class.getSimpleName(), "视频信息:" + videoFrame.getRotatedWidth() + " - " + videoFrame.getRotatedHeight());
|
||||||
|
try {
|
||||||
|
vq.put(videoFrame);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
// final int videoFrameSize = videoFrame.getRotatedWidth() * videoFrame.getRotatedHeight() * 3 / 2;
|
||||||
|
// final int index = this.videoCodec.dequeueInputBuffer(WAIT_TIME_US);
|
||||||
|
// if(index < 0) {
|
||||||
|
// videoFrame.retain();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// final VideoFrame.I420Buffer i420 = videoFrame.getBuffer().toI420();
|
||||||
|
// final ByteBuffer inputByteBuffer = this.videoCodec.getInputBuffer(index);
|
||||||
|
// YuvHelper.I420ToNV12(i420.getDataY(), i420.getStrideY(), i420.getDataU(), i420.getStrideU(), i420.getDataV(), i420.getStrideV(), inputByteBuffer, i420.getWidth(), i420.getHeight());
|
||||||
|
// i420.release();
|
||||||
|
// videoFrame.release();
|
||||||
|
// this.videoCodec.queueInputBuffer(index, 0, videoFrameSize, videoFrame.getTimestampNs(), 0);
|
||||||
|
// videoFrame = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockingQueue<VideoFrame> vq = new LinkedBlockingQueue<>();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ public class Room extends CloseableClient implements RouterCallback {
|
|||||||
}
|
}
|
||||||
super.init();
|
super.init();
|
||||||
this.peerConnectionFactory = this.mediaManager.newClient(VideoSourceType.BACK);
|
this.peerConnectionFactory = this.mediaManager.newClient(VideoSourceType.BACK);
|
||||||
this.mediaManager.startVideoCapture();
|
|
||||||
this.localClient = new LocalClient(this.name, this.clientId, this.taoyao, this.mainHandler);
|
this.localClient = new LocalClient(this.name, this.clientId, this.taoyao, this.mainHandler);
|
||||||
this.localClient.setMediaStream(this.mediaManager.getMediaStream());
|
this.localClient.setMediaStream(this.mediaManager.getMediaStream());
|
||||||
// STUN | TURN
|
// STUN | TURN
|
||||||
@@ -222,7 +221,6 @@ public class Room extends CloseableClient implements RouterCallback {
|
|||||||
this.remoteClients.values().forEach(v -> this.closeRemoteClient(v.clientId));
|
this.remoteClients.values().forEach(v -> this.closeRemoteClient(v.clientId));
|
||||||
this.remoteClients.clear();
|
this.remoteClients.clear();
|
||||||
this.localClient.close();
|
this.localClient.close();
|
||||||
this.mediaManager.stopVideoCapture();
|
|
||||||
this.mediaManager.closeClient();
|
this.mediaManager.closeClient();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,6 @@ public class SessionClient extends Client {
|
|||||||
}
|
}
|
||||||
super.init();
|
super.init();
|
||||||
this.peerConnectionFactory = this.mediaManager.newClient(VideoSourceType.BACK);
|
this.peerConnectionFactory = this.mediaManager.newClient(VideoSourceType.BACK);
|
||||||
this.mediaManager.startVideoCapture();
|
|
||||||
// STUN | TURN
|
// STUN | TURN
|
||||||
final List<PeerConnection.IceServer> iceServers = new ArrayList<>();
|
final List<PeerConnection.IceServer> iceServers = new ArrayList<>();
|
||||||
// TODO:读取配置
|
// TODO:读取配置
|
||||||
@@ -281,7 +280,6 @@ public class SessionClient extends Client {
|
|||||||
}
|
}
|
||||||
super.close();
|
super.close();
|
||||||
this.remoteMediaStream.dispose();
|
this.remoteMediaStream.dispose();
|
||||||
this.mediaManager.stopVideoCapture();
|
|
||||||
this.mediaManager.closeClient();
|
this.mediaManager.closeClient();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user