[*] 复用

This commit is contained in:
acgist
2023-04-16 12:15:30 +08:00
parent 288447c4dc
commit a08462a3f2
6 changed files with 180 additions and 213 deletions

View File

@@ -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();
} }
} }

View File

@@ -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) {
this.shareClientCount++;
return;
} else {
this.shareClientCount++;
this.videoCapturer.startCapture(this.mediaVideoProperties.getWidth(), this.mediaVideoProperties.getHeight(), this.mediaVideoProperties.getFrameRate());
}
} }
final MediaVideoProperties mediaVideoProperties = this.mediaProperties.getVideos().get(this.videoQuantity);
this.videoCapturer.startCapture(mediaVideoProperties.getWidth(), mediaVideoProperties.getHeight(), mediaVideoProperties.getFrameRate());
} }
public void stopVideoCapture() { private void stopVideoCapture() {
synchronized (this) { if(this.videoCapturer == null) {
if(this.videoCapturer == null) { return;
return; }
} try {
if(this.shareClientCount <= 0) { videoCapturer.stopCapture();
return; } catch (InterruptedException e) {
} Log.e(MediaManager.class.getSimpleName(), "关闭视频捕获异常", e);
this.shareClientCount--;
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);
}
} }
/** /**

View File

@@ -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;

View File

@@ -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<>();
} }

View File

@@ -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();
} }
} }

View File

@@ -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();
} }
} }