diff --git a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt index c32d311fb..3e2df4b77 100644 --- a/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt +++ b/app/src/main/java/com/pedro/streamer/rotation/CameraFragment.kt @@ -181,8 +181,8 @@ class CameraFragment: Fragment(), ConnectChecker { private fun prepare() { val prepared = try { - genericStream.prepareVideo(width, height, vBitrate, rotation = rotation) && - genericStream.prepareAudio(sampleRate, isStereo, aBitrate) + genericStream.prepareVideo(width, height, vBitrate, rotation = rotation) + && genericStream.prepareAudio(sampleRate, isStereo, aBitrate) } catch (e: IllegalArgumentException) { false } diff --git a/encoder/src/main/java/com/pedro/encoder/input/sources/video/VideoFileSource.kt b/encoder/src/main/java/com/pedro/encoder/input/sources/video/VideoFileSource.kt index b7ede3bf7..80a89397a 100644 --- a/encoder/src/main/java/com/pedro/encoder/input/sources/video/VideoFileSource.kt +++ b/encoder/src/main/java/com/pedro/encoder/input/sources/video/VideoFileSource.kt @@ -93,22 +93,19 @@ class VideoFileSource( @Throws(IOException::class) fun replaceFile(context: Context, uri: Uri) { - val width = videoDecoder.width - val height = videoDecoder.height val wasRunning = videoDecoder.isRunning val videoDecoder = VideoDecoder(videoDecoderInterface, decoderInterface) videoDecoder.extractor = this.videoDecoder.extractor if (!videoDecoder.initExtractor(context, uri)) throw IOException("Extraction failed") - if (width != videoDecoder.width || height != videoDecoder.height) throw IOException("Resolution must be the same that the previous file") this.videoDecoder.stop() this.videoDecoder = videoDecoder if (wasRunning) { videoDecoder.prepareVideo(Surface(surfaceTexture)) videoDecoder.start() } + } - fun setExtractor(extractor: Extractor) { - videoDecoder.extractor = extractor - } + fun setExtractor(extractor: Extractor) { + videoDecoder.extractor = extractor } } \ No newline at end of file diff --git a/library/src/main/java/com/pedro/library/base/Camera2Base.java b/library/src/main/java/com/pedro/library/base/Camera2Base.java index 2dc286479..bd57c6fbc 100644 --- a/library/src/main/java/com/pedro/library/base/Camera2Base.java +++ b/library/src/main/java/com/pedro/library/base/Camera2Base.java @@ -83,10 +83,12 @@ public abstract class Camera2Base { private final Context context; private Camera2ApiManager cameraManager; protected VideoEncoder videoEncoder; + protected VideoEncoder videoEncoderRecord; private MicrophoneManager microphoneManager; private AudioEncoder audioEncoder; private boolean streaming = false; private GlInterface glInterface; + private boolean differentRecordResolution = false; protected boolean audioInitialized = false; private boolean onPreview = false; private boolean isBackground = false; @@ -111,6 +113,7 @@ private void init(Context context) { cameraManager = new Camera2ApiManager(context); microphoneManager = new MicrophoneManager(getMicrophoneData); videoEncoder = new VideoEncoder(getVideoData); + videoEncoderRecord = new VideoEncoder(getVideoDataRecord); audioEncoder = new AudioEncoder(getAudioData); recordController = new AndroidMuxerRecordController(); } @@ -125,6 +128,7 @@ public void setCameraCallbacks(CameraCallbacks callbacks) { */ public void setTimestampMode(TimestampMode timestampModeVideo, TimestampMode timestampModeAudio) { videoEncoder.setTimestampMode(timestampModeVideo); + videoEncoderRecord.setTimestampMode(timestampModeVideo); audioEncoder.setTimestampMode(timestampModeAudio); } @@ -134,6 +138,7 @@ public void setTimestampMode(TimestampMode timestampModeVideo, TimestampMode tim */ public void setEncoderErrorCallback(EncoderErrorCallback encoderErrorCallback) { videoEncoder.setEncoderErrorCallback(encoderErrorCallback); + videoEncoderRecord.setEncoderErrorCallback(encoderErrorCallback); audioEncoder.setEncoderErrorCallback(encoderErrorCallback); } @@ -249,6 +254,12 @@ public String getCurrentCameraId() { } public boolean resetVideoEncoder() { + if (differentRecordResolution) { + glInterface.removeMediaCodecRecordSurface(); + boolean result = videoEncoderRecord.reset(); + if (!result) return false; + glInterface.addMediaCodecRecordSurface(videoEncoderRecord.getInputSurface()); + } glInterface.removeMediaCodecSurface(); boolean result = videoEncoder.reset(); if (!result) return false; @@ -275,16 +286,39 @@ public boolean resetAudioEncoder() { * @return true if success, false if you get a error (Normally because the encoder selected * doesn't support any configuration seated or your device hasn't a H264 encoder). */ - public boolean prepareVideo(int width, int height, int fps, int bitrate, int iFrameInterval, - int rotation, int profile, int level) { + public boolean prepareVideo( + int width, int height, int fps, int bitrate, int iFrameInterval, + int rotation, int profile, int level, + int recordWidth, int recordHeight, int recordBitrate + ) { if (onPreview && (width != previewWidth || height != previewHeight || fps != videoEncoder.getFps() || rotation != videoEncoder.getRotation())) { stopPreview(); } + differentRecordResolution = false; + if (recordWidth != width && recordHeight != height) { + if ((double) recordWidth / (double) recordHeight != (double) width / (double) height) { + Log.e(TAG, "The aspect ratio of record and stream resolution must be the same"); + return false; + } + differentRecordResolution = true; + } + if (differentRecordResolution) { + boolean result = videoEncoderRecord.prepareVideoEncoder(recordWidth, recordHeight, fps, recordBitrate, rotation, + iFrameInterval, FormatVideoEncoder.SURFACE, profile, level); + if (!result) return false; + } return videoEncoder.prepareVideoEncoder(width, height, fps, bitrate, rotation, iFrameInterval, FormatVideoEncoder.SURFACE, profile, level); } + public boolean prepareVideo( + int width, int height, int fps, int bitrate, int iFrameInterval, + int rotation, int profile, int level + ) { + return prepareVideo(width, height, fps, bitrate, iFrameInterval, rotation, profile, level, width, height, bitrate); + } + public boolean prepareVideo(int width, int height, int fps, int bitrate, int iFrameInterval, int rotation) { return prepareVideo(width, height, fps, bitrate, iFrameInterval, rotation, -1, -1); @@ -364,6 +398,7 @@ public boolean prepareAudio() { */ public void forceCodecType(CodecUtil.CodecType codecTypeVideo, CodecUtil.CodecType codecTypeAudio) { videoEncoder.forceCodecType(codecTypeVideo); + videoEncoderRecord.forceCodecType(codecTypeVideo); audioEncoder.forceCodecType(codecTypeAudio); } @@ -378,7 +413,7 @@ public void startRecord(@NonNull String path, @Nullable RecordController.Listene recordController.startRecord(path, listener); if (!streaming) { startEncoders(); - } else if (videoEncoder.isRunning()) { + } else if (videoEncoder.isRunning() || videoEncoderRecord.isRunning()) { requestKeyFrame(); } } @@ -399,7 +434,7 @@ public void startRecord(@NonNull final FileDescriptor fd, recordController.startRecord(fd, listener); if (!streaming) { startEncoders(); - } else if (videoEncoder.isRunning()) { + } else if (videoEncoder.isRunning() || videoEncoderRecord.isRunning()) { requestKeyFrame(); } } @@ -434,18 +469,24 @@ public void replaceView(OpenGlView openGlView) { private void replaceGlInterface(GlInterface glInterface) { if (isStreaming() || isRecording() || isOnPreview()) { Point size = this.glInterface.getEncoderSize(); + Point sizeRecord = this.glInterface.getEncoderSize(); cameraManager.closeCamera(); this.glInterface.removeMediaCodecSurface(); + this.glInterface.removeMediaCodecRecordSurface(); this.glInterface.stop(); this.glInterface = glInterface; int w = size.x; int h = size.y; + int recordW = sizeRecord.x; + int recordH = sizeRecord.y; int rotation = videoEncoder.getRotation(); if (rotation == 90 || rotation == 270) { h = size.x; w = size.y; + recordH = sizeRecord.x; + recordW = sizeRecord.y; } - prepareGlView(w, h, rotation); + prepareGlView(w, h, recordW, recordH, rotation); cameraManager.openLastCamera(); } else { this.glInterface = glInterface; @@ -478,7 +519,9 @@ public void startPreview(String cameraId, int width, int height, int fps, int ro previewHeight = height; videoEncoder.setFps(fps); videoEncoder.setRotation(rotation); - prepareGlView(width, height, rotation); + videoEncoderRecord.setFps(fps); + videoEncoderRecord.setRotation(rotation); + prepareGlView(width, height, width, height, rotation); cameraManager.openCameraId(cameraId); onPreview = true; } else if (!isStreaming() && !onPreview && isBackground) { @@ -588,8 +631,9 @@ public void startStream(String url) { private void startEncoders() { long startTs = System.nanoTime() / 1000; videoEncoder.start(startTs); + if (differentRecordResolution) videoEncoderRecord.start(startTs); if (audioInitialized) audioEncoder.start(startTs); - prepareGlView(videoEncoder.getWidth(), videoEncoder.getHeight(), videoEncoder.getRotation()); + prepareGlView(videoEncoder.getWidth(), videoEncoder.getHeight(), videoEncoderRecord.getWidth(), videoEncoderRecord.getHeight(), videoEncoder.getRotation()); if (audioInitialized) microphoneManager.start(); if (!cameraManager.isRunning()) cameraManager.openLastCamera(); onPreview = true; @@ -599,18 +643,26 @@ public void requestKeyFrame() { if (videoEncoder.isRunning()) { videoEncoder.requestKeyframe(); } + if (videoEncoderRecord.isRunning()) { + videoEncoderRecord.requestKeyframe(); + } } - private void prepareGlView(int width, int height, int rotation) { + private void prepareGlView(int width, int height, int recordWidth, int recordHeight, int rotation) { int w = width; int h = height; + int recordW = recordWidth; + int recordH = recordHeight; boolean isPortrait = false; if (rotation == 90 || rotation == 270) { h = width; w = height; + recordH = recordWidth; + recordW = recordHeight; isPortrait = true; } glInterface.setEncoderSize(w, h); + if (differentRecordResolution) glInterface.setEncoderRecordSize(recordW, recordH); if (glInterface instanceof GlStreamInterface glStreamInterface) { glStreamInterface.setPreviewResolution(w, h); glStreamInterface.setIsPortrait(isPortrait); @@ -620,8 +672,12 @@ private void prepareGlView(int width, int height, int rotation) { if (videoEncoder.getInputSurface() != null && videoEncoder.isRunning()) { glInterface.addMediaCodecSurface(videoEncoder.getInputSurface()); } - cameraManager.prepareCamera(glInterface.getSurfaceTexture(), videoEncoder.getWidth(), - videoEncoder.getHeight(), videoEncoder.getFps()); + if (videoEncoderRecord.getInputSurface() != null && videoEncoderRecord.isRunning()) { + glInterface.addMediaCodecRecordSurface(videoEncoderRecord.getInputSurface()); + } + int cameraWidth = Math.max(videoEncoder.getWidth(), videoEncoderRecord.getWidth()); + int cameraHeight = Math.max(videoEncoder.getHeight(), videoEncoderRecord.getHeight()); + cameraManager.prepareCamera(glInterface.getSurfaceTexture(), cameraWidth, cameraHeight, videoEncoder.getFps()); } protected abstract void stopStreamImp(); @@ -638,11 +694,13 @@ public void stopStream() { onPreview = !isBackground; if (audioInitialized) microphoneManager.stop(); glInterface.removeMediaCodecSurface(); + glInterface.removeMediaCodecRecordSurface(); if (glInterface instanceof GlStreamInterface) { glInterface.stop(); cameraManager.closeCamera(); } videoEncoder.stop(); + if (differentRecordResolution) videoEncoderRecord.stop(); if (audioInitialized) audioEncoder.stop(); recordController.resetFormats(); } @@ -864,6 +922,7 @@ public void setVideoBitrateOnFly(int bitrate) { public void forceFpsLimit(boolean enabled) { int fps = enabled ? videoEncoder.getFps() : 0; videoEncoder.setForceFps(fps); + videoEncoderRecord.setForceFps(fps); glInterface.forceFpsLimit(fps); } @@ -961,10 +1020,26 @@ public void onVideoInfo(@NonNull ByteBuffer sps, @Nullable ByteBuffer pps, @Null @Override public void getVideoData(@NonNull ByteBuffer videoBuffer, @NonNull MediaCodec.BufferInfo info) { fpsListener.calculateFps(); - recordController.recordVideo(videoBuffer, info); + if (!differentRecordResolution) recordController.recordVideo(videoBuffer, info); if (streaming) getVideoDataImp(videoBuffer, info); } + @Override + public void onVideoFormat(@NonNull MediaFormat mediaFormat) { + if (!differentRecordResolution) recordController.setVideoFormat(mediaFormat, !audioInitialized); + } + }; + + private final GetVideoData getVideoDataRecord = new GetVideoData() { + @Override + public void onVideoInfo(@NonNull ByteBuffer sps, @Nullable ByteBuffer pps, @Nullable ByteBuffer vps) { + } + + @Override + public void getVideoData(@NonNull ByteBuffer videoBuffer, @NonNull MediaCodec.BufferInfo info) { + recordController.recordVideo(videoBuffer, info); + } + @Override public void onVideoFormat(@NonNull MediaFormat mediaFormat) { recordController.setVideoFormat(mediaFormat, !audioInitialized); diff --git a/library/src/main/java/com/pedro/library/base/StreamBase.kt b/library/src/main/java/com/pedro/library/base/StreamBase.kt index 7366d561c..b6b9c4ed0 100644 --- a/library/src/main/java/com/pedro/library/base/StreamBase.kt +++ b/library/src/main/java/com/pedro/library/base/StreamBase.kt @@ -50,6 +50,7 @@ import com.pedro.library.util.FpsListener import com.pedro.library.util.streamclient.StreamBaseClient import com.pedro.library.view.GlStreamInterface import java.nio.ByteBuffer +import kotlin.math.max /** @@ -74,6 +75,7 @@ abstract class StreamBase( } //video and audio encoders private val videoEncoder by lazy { VideoEncoder(getVideoData) } + private val videoEncoderRecord by lazy { VideoEncoder(getVideoDataRecord) } private val audioEncoder by lazy { AudioEncoder(getAacData) } //video render private val glInterface = GlStreamInterface(context) @@ -90,6 +92,7 @@ abstract class StreamBase( private set var audioSource: AudioSource = aSource private set + private var differentRecordResolution = false /** * Necessary only one time before start preview, stream or record. @@ -99,23 +102,44 @@ abstract class StreamBase( * @param level codec value from MediaCodecInfo.CodecProfileLevel class * * @throws IllegalArgumentException if current video parameters are not supported by the VideoSource + * @throws IllegalArgumentException if you use differentRecordResolution but the aspect ratio is not the same than stream resolution * @return True if success, False if failed */ @Throws(IllegalArgumentException::class) @JvmOverloads - fun prepareVideo(width: Int, height: Int, bitrate: Int, fps: Int = 30, iFrameInterval: Int = 2, - rotation: Int = 0, profile: Int = -1, level: Int = -1): Boolean { + fun prepareVideo( + width: Int, height: Int, bitrate: Int, fps: Int = 30, iFrameInterval: Int = 2, + rotation: Int = 0, profile: Int = -1, level: Int = -1, + recordWidth: Int = width, recordHeight: Int = height, recordBitrate: Int = bitrate + ): Boolean { if (isStreaming || isRecording || isOnPreview) { throw IllegalStateException("Stream, record and preview must be stopped before prepareVideo") } - val videoResult = videoSource.init(width, height, fps, rotation) + differentRecordResolution = false + if (recordWidth != width && recordHeight != height) { + if (recordWidth.toDouble() / recordHeight.toDouble() != width.toDouble() / height.toDouble()) { + throw IllegalArgumentException("The aspect ratio of record and stream resolution must be the same") + } + differentRecordResolution = true + } + val videoResult = videoSource.init(max(width, recordWidth), max(height, recordHeight), fps, rotation) if (videoResult) { + if (differentRecordResolution) { + //using different record resolution + if (rotation == 90 || rotation == 270) glInterface.setEncoderRecordSize(recordHeight, recordWidth) + else glInterface.setEncoderRecordSize(recordWidth, recordHeight) + } if (rotation == 90 || rotation == 270) glInterface.setEncoderSize(height, width) else glInterface.setEncoderSize(width, height) val isPortrait = rotation == 90 || rotation == 270 glInterface.setIsPortrait(isPortrait) glInterface.setCameraOrientation(if (rotation == 0) 270 else rotation - 90) glInterface.forceOrientation(videoSource.getOrientationConfig()) + if (differentRecordResolution) { + val result = videoEncoderRecord.prepareVideoEncoder(recordWidth, recordHeight, fps, recordBitrate, rotation, + iFrameInterval, FormatVideoEncoder.SURFACE, profile, level) + if (!result) return false + } return videoEncoder.prepareVideoEncoder(width, height, fps, bitrate, rotation, iFrameInterval, FormatVideoEncoder.SURFACE, profile, level) } @@ -165,6 +189,9 @@ abstract class StreamBase( if (videoEncoder.isRunning) { videoEncoder.requestKeyframe() } + if (videoEncoderRecord.isRunning) { + videoEncoderRecord.requestKeyframe() + } } /** @@ -185,6 +212,7 @@ abstract class StreamBase( fun forceFpsLimit(enabled: Boolean) { val fps = if (enabled) videoEncoder.fps else 0 videoEncoder.setForceFps(fps) + videoEncoderRecord.setForceFps(fps) glInterface.forceFpsLimit(fps) } @@ -194,6 +222,7 @@ abstract class StreamBase( */ fun forceCodecType(codecTypeVideo: CodecUtil.CodecType, codecTypeAudio: CodecUtil.CodecType) { videoEncoder.forceCodecType(codecTypeVideo) + videoEncoderRecord.forceCodecType(codecTypeVideo) audioEncoder.forceCodecType(codecTypeAudio) } @@ -224,7 +253,10 @@ abstract class StreamBase( if (isRecording) throw IllegalStateException("Record already started, stopRecord before startRecord again") recordController.startRecord(path, listener) if (!isStreaming) startSources() - else videoEncoder.requestKeyframe() + else { + videoEncoder.requestKeyframe() + videoEncoderRecord.requestKeyframe() + } } /** @@ -356,7 +388,15 @@ abstract class StreamBase( fun changeVideoSource(source: VideoSource) { val wasRunning = videoSource.isRunning() val wasCreated = videoSource.created - if (wasCreated) source.init(videoEncoder.width, videoEncoder.height, videoEncoder.fps, videoEncoder.rotation) + if (wasCreated) { + var width = videoEncoder.width + var height = videoEncoder.height + if (differentRecordResolution) { + width = max(width, videoEncoderRecord.width) + height = max(height, videoEncoderRecord.height) + } + source.init(width, height, videoEncoder.fps, videoEncoder.rotation) + } videoSource.stop() videoSource.release() if (wasRunning) source.start(glInterface.surfaceTexture) @@ -387,6 +427,7 @@ abstract class StreamBase( */ fun setTimestampMode(timestampModeVideo: TimestampMode, timestampModeAudio: TimestampMode) { videoEncoder.setTimestampMode(timestampModeVideo) + videoEncoderRecord.setTimestampMode(timestampModeVideo) audioEncoder.setTimestampMode(timestampModeAudio) } @@ -396,6 +437,7 @@ abstract class StreamBase( */ fun setEncoderErrorCallback(encoderErrorCallback: EncoderErrorCallback?) { videoEncoder.setEncoderErrorCallback(encoderErrorCallback) + videoEncoderRecord.setEncoderErrorCallback(encoderErrorCallback) audioEncoder.setEncoderErrorCallback(encoderErrorCallback) } @@ -453,16 +495,20 @@ abstract class StreamBase( audioSource.start(getMicrophoneData) val startTs = System.nanoTime() / 1000 videoEncoder.start(startTs) + if (differentRecordResolution) videoEncoderRecord.start(startTs) audioEncoder.start(startTs) glInterface.addMediaCodecSurface(videoEncoder.inputSurface) + if (differentRecordResolution) glInterface.addMediaCodecRecordSurface(videoEncoderRecord.inputSurface) } private fun stopSources() { if (!isOnPreview) videoSource.stop() audioSource.stop() glInterface.removeMediaCodecSurface() + glInterface.removeMediaCodecRecordSurface() if (!isOnPreview) glInterface.stop() videoEncoder.stop() + videoEncoderRecord.stop() audioEncoder.stop() if (!isRecording) recordController.resetFormats() } @@ -486,6 +532,12 @@ abstract class StreamBase( * @return true if success, false if failed */ fun resetVideoEncoder(): Boolean { + if (differentRecordResolution) { + glInterface.removeMediaCodecRecordSurface() + val result = videoEncoderRecord.reset() + if (!result) return false + glInterface.addMediaCodecRecordSurface(videoEncoderRecord.inputSurface) + } glInterface.removeMediaCodecSurface() val result = videoEncoder.reset() if (!result) return false @@ -501,6 +553,10 @@ abstract class StreamBase( fun resetAudioEncoder(): Boolean = audioEncoder.reset() private fun prepareEncoders(): Boolean { + if (differentRecordResolution) { + val result = videoEncoderRecord.prepareVideoEncoder() + if (!result) return false + } return videoEncoder.prepareVideoEncoder() && audioEncoder.prepareAudioEncoder() } @@ -523,6 +579,19 @@ abstract class StreamBase( override fun getVideoData(videoBuffer: ByteBuffer, info: MediaCodec.BufferInfo) { fpsListener.calculateFps() getVideoDataImp(videoBuffer, info) + if (!differentRecordResolution) recordController.recordVideo(videoBuffer, info) + } + + override fun onVideoFormat(mediaFormat: MediaFormat) { + if (!differentRecordResolution) recordController.setVideoFormat(mediaFormat) + } + } + + private val getVideoDataRecord: GetVideoData = object : GetVideoData { + override fun onVideoInfo(sps: ByteBuffer, pps: ByteBuffer?, vps: ByteBuffer?) { + } + + override fun getVideoData(videoBuffer: ByteBuffer, info: MediaCodec.BufferInfo) { recordController.recordVideo(videoBuffer, info) } @@ -553,6 +622,7 @@ abstract class StreamBase( VideoCodec.AV1 -> CodecUtil.AV1_MIME } videoEncoder.type = type + videoEncoderRecord.type = type } /** diff --git a/library/src/main/java/com/pedro/library/view/GlInterface.java b/library/src/main/java/com/pedro/library/view/GlInterface.java index 8984e0d98..4ef92c872 100644 --- a/library/src/main/java/com/pedro/library/view/GlInterface.java +++ b/library/src/main/java/com/pedro/library/view/GlInterface.java @@ -33,6 +33,8 @@ public interface GlInterface { */ void setEncoderSize(int width, int height); + void setEncoderRecordSize(int width, int height); + Point getEncoderSize(); /** * Get SurfaceTexture generated by Opengl. This should be called after start render. @@ -58,6 +60,9 @@ public interface GlInterface { */ void removeMediaCodecSurface(); + void addMediaCodecRecordSurface(Surface surface); + + void removeMediaCodecRecordSurface(); /** * Capture an Image from Opengl. * diff --git a/library/src/main/java/com/pedro/library/view/GlStreamInterface.kt b/library/src/main/java/com/pedro/library/view/GlStreamInterface.kt index 19073b34a..d3f241a38 100644 --- a/library/src/main/java/com/pedro/library/view/GlStreamInterface.kt +++ b/library/src/main/java/com/pedro/library/view/GlStreamInterface.kt @@ -53,11 +53,14 @@ class GlStreamInterface(private val context: Context): OnFrameAvailableListener, private val running = AtomicBoolean(false) private val surfaceManager = SurfaceManager() private val surfaceManagerEncoder = SurfaceManager() + private val surfaceManagerEncoderRecord = SurfaceManager() private val surfaceManagerPhoto = SurfaceManager() private val surfaceManagerPreview = SurfaceManager() private val mainRender = MainRender() private var encoderWidth = 0 private var encoderHeight = 0 + private var encoderRecordWidth = 0 + private var encoderRecordHeight = 0 private var streamOrientation = 0 private var previewWidth = 0 private var previewHeight = 0 @@ -90,6 +93,11 @@ class GlStreamInterface(private val context: Context): OnFrameAvailableListener, encoderHeight = height } + override fun setEncoderRecordSize(width: Int, height: Int) { + encoderRecordWidth = width + encoderRecordHeight = height + } + override fun getEncoderSize(): Point { return Point(encoderWidth, encoderHeight) } @@ -138,6 +146,22 @@ class GlStreamInterface(private val context: Context): OnFrameAvailableListener, } } + override fun addMediaCodecRecordSurface(surface: Surface) { + executor?.secureSubmit { + if (surfaceManager.isReady) { + surfaceManagerEncoderRecord.release() + surfaceManagerEncoderRecord.eglSetup(surface, surfaceManager) + } + } + } + + override fun removeMediaCodecRecordSurface() { + threadQueue.clear() + executor?.secureSubmit { + surfaceManagerEncoderRecord.release() + } + } + override fun takePhoto(takePhotoCallback: TakePhotoCallback?) { this.takePhotoCallback = takePhotoCallback } @@ -166,6 +190,7 @@ class GlStreamInterface(private val context: Context): OnFrameAvailableListener, sensorRotationManager.stop() surfaceManagerPhoto.release() surfaceManagerEncoder.release() + surfaceManagerEncoderRecord.release() surfaceManager.release() mainRender.release() } @@ -209,6 +234,15 @@ class GlStreamInterface(private val context: Context): OnFrameAvailableListener, isStreamVerticalFlip, isStreamHorizontalFlip) surfaceManagerEncoder.swapBuffer() } + // render VideoEncoder (record if the resolution is different than stream) + if (surfaceManagerEncoderRecord.isReady && mainRender.isReady() && !limitFps) { + val w = if (muteVideo) 0 else encoderRecordWidth + val h = if (muteVideo) 0 else encoderRecordHeight + surfaceManagerEncoderRecord.makeCurrent() + mainRender.drawScreenEncoder(w, h, orientation, streamOrientation, + isStreamVerticalFlip, isStreamHorizontalFlip) + surfaceManagerEncoderRecord.swapBuffer() + } //render surface photo if request photo if (takePhotoCallback != null && surfaceManagerPhoto.isReady && mainRender.isReady()) { surfaceManagerPhoto.makeCurrent() diff --git a/library/src/main/java/com/pedro/library/view/OpenGlView.java b/library/src/main/java/com/pedro/library/view/OpenGlView.java index 44aef00ba..9dea65260 100644 --- a/library/src/main/java/com/pedro/library/view/OpenGlView.java +++ b/library/src/main/java/com/pedro/library/view/OpenGlView.java @@ -60,10 +60,12 @@ public class OpenGlView extends SurfaceView private final SurfaceManager surfaceManagerPhoto = new SurfaceManager(); private final SurfaceManager surfaceManager = new SurfaceManager(); private final SurfaceManager surfaceManagerEncoder = new SurfaceManager(); + private final SurfaceManager surfaceManagerEncoderRecord = new SurfaceManager(); private final BlockingQueue filterQueue = new LinkedBlockingQueue<>(); private final LinkedBlockingQueue threadQueue = new LinkedBlockingQueue<>(); private int previewWidth, previewHeight; private int encoderWidth, encoderHeight; + private int encoderRecordWidth, encoderRecordHeight; private TakePhotoCallback takePhotoCallback; private int streamRotation; private boolean muteVideo = false; @@ -224,6 +226,12 @@ public void setEncoderSize(int width, int height) { this.encoderHeight = height; } + @Override + public void setEncoderRecordSize(int width, int height) { + this.encoderRecordWidth = width; + this.encoderRecordHeight = height; + } + @Override public Point getEncoderSize() { return new Point(encoderWidth, encoderHeight); @@ -267,6 +275,15 @@ private void draw(boolean forced) { streamRotation, isStreamVerticalFlip, isStreamHorizontalFlip); surfaceManagerEncoder.swapBuffer(); } + // render VideoEncoder (record if the resolution is different than stream) + if (surfaceManagerEncoderRecord.isReady() && mainRender.isReady() && !limitFps) { + int w = muteVideo ? 0 : encoderRecordWidth; + int h = muteVideo ? 0 : encoderRecordHeight; + surfaceManagerEncoderRecord.makeCurrent(); + mainRender.drawScreen(w, h, aspectRatioMode, + streamRotation, isStreamVerticalFlip, isStreamHorizontalFlip); + surfaceManagerEncoderRecord.swapBuffer(); + } if (takePhotoCallback != null && surfaceManagerPhoto.isReady() && mainRender.isReady()) { surfaceManagerPhoto.makeCurrent(); mainRender.drawScreen(encoderWidth, encoderHeight, aspectRatioMode, @@ -283,10 +300,8 @@ public void addMediaCodecSurface(Surface surface) { if (executor == null) return; ExtensionsKt.secureSubmit(executor, () -> { if (surfaceManager.isReady()) { - surfaceManagerPhoto.release(); surfaceManagerEncoder.release(); surfaceManagerEncoder.eglSetup(surface, surfaceManager); - surfaceManagerPhoto.eglSetup(encoderWidth, encoderHeight, surfaceManagerEncoder); } return null; }); @@ -298,9 +313,31 @@ public void removeMediaCodecSurface() { ExecutorService executor = this.executor; if (executor == null) return; ExtensionsKt.secureSubmit(executor, () -> { - surfaceManagerPhoto.release(); surfaceManagerEncoder.release(); - surfaceManagerPhoto.eglSetup(encoderWidth, encoderHeight, surfaceManager); + return null; + }); + } + + @Override + public void addMediaCodecRecordSurface(Surface surface) { + ExecutorService executor = this.executor; + if (executor == null) return; + ExtensionsKt.secureSubmit(executor, () -> { + if (surfaceManager.isReady()) { + surfaceManagerEncoderRecord.release(); + surfaceManagerEncoderRecord.eglSetup(surface, surfaceManager); + } + return null; + }); + } + + @Override + public void removeMediaCodecRecordSurface() { + threadQueue.clear(); + ExecutorService executor = this.executor; + if (executor == null) return; + ExtensionsKt.secureSubmit(executor, () -> { + surfaceManagerEncoderRecord.release(); return null; }); } @@ -338,6 +375,7 @@ public void stop() { forceRenderer.stop(); surfaceManagerPhoto.release(); surfaceManagerEncoder.release(); + surfaceManagerEncoderRecord.release(); surfaceManager.release(); mainRender.release(); return null;