From 6644456a958478970c0094974c296dea43d61c68 Mon Sep 17 00:00:00 2001 From: xufuji456 Date: Fri, 1 Apr 2022 14:20:53 +0800 Subject: [PATCH] Feature: add screen living stream --- .../controller/MediaProjectionController.kt | 67 +++++++++++++++++++ .../listener/VideoEncodeCallback.kt | 12 ++++ 2 files changed, 79 insertions(+) create mode 100644 AndroidMedia/src/main/java/com/frank/androidmedia/listener/VideoEncodeCallback.kt diff --git a/AndroidMedia/src/main/java/com/frank/androidmedia/controller/MediaProjectionController.kt b/AndroidMedia/src/main/java/com/frank/androidmedia/controller/MediaProjectionController.kt index 31824e4..48e615f 100644 --- a/AndroidMedia/src/main/java/com/frank/androidmedia/controller/MediaProjectionController.kt +++ b/AndroidMedia/src/main/java/com/frank/androidmedia/controller/MediaProjectionController.kt @@ -8,12 +8,17 @@ import android.graphics.PixelFormat import android.hardware.display.DisplayManager import android.hardware.display.VirtualDisplay import android.media.ImageReader +import android.media.MediaCodec +import android.media.MediaCodecInfo +import android.media.MediaFormat import android.media.projection.MediaProjection import android.media.projection.MediaProjectionManager import android.os.Environment import android.util.DisplayMetrics +import android.util.Log import android.view.Surface import android.view.WindowManager +import com.frank.androidmedia.listener.VideoEncodeCallback import java.io.FileOutputStream import java.lang.Exception @@ -39,10 +44,20 @@ open class MediaProjectionController(type: Int) { private var mediaProjection: MediaProjection? = null private var mediaProjectionManager: MediaProjectionManager? = null + private var encodeThread: Thread? = null + private var videoEncoder: MediaCodec? = null + private var isVideoEncoding = false + private var videoEncodeData: ByteArray? = null + private var videoEncodeCallback: VideoEncodeCallback? = null + init { this.type = type } + fun setVideoEncodeListener(encodeCallback: VideoEncodeCallback) { + videoEncodeCallback = encodeCallback + } + fun startScreenRecord(context: Context) { val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager displayMetrics = DisplayMetrics() @@ -95,10 +110,61 @@ open class MediaProjectionController(type: Int) { }, null) } + private fun initMediaCodec(width: Int, height: Int) { + val mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height) + mediaFormat.setInteger(MediaFormat.KEY_WIDTH, width) + mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, height) + mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 20) + mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height) + mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 3) + mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) + + videoEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC) + videoEncoder?.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) + createVirtualDisplay(videoEncoder!!.createInputSurface()) + } + + private fun startVideoEncoder() { + if (videoEncoder == null || isVideoEncoding) + return + encodeThread = Thread { + try { + val bufferInfo = MediaCodec.BufferInfo() + videoEncoder?.start() + + while (isVideoEncoding && !Thread.currentThread().isInterrupted) { + val outputIndex = videoEncoder!!.dequeueOutputBuffer(bufferInfo, 30 * 1000) + if (outputIndex >= 0) { + val byteBuffer = videoEncoder!!.getOutputBuffer(outputIndex) + if (videoEncodeData == null || videoEncodeData!!.size < bufferInfo.size) { + videoEncodeData = ByteArray(bufferInfo.size) + } + if (videoEncodeCallback != null && byteBuffer != null) { + byteBuffer.get(videoEncodeData, bufferInfo.offset, bufferInfo.size) + videoEncodeCallback!!.onVideoEncodeData(videoEncodeData!!, bufferInfo.size, + bufferInfo.flags, bufferInfo.presentationTimeUs) + } + videoEncoder!!.releaseOutputBuffer(outputIndex, false) + } else { + Log.e("EncodeThread", "invalid index=$outputIndex") + } + } + } catch (e: Exception) { + isVideoEncoding = false + Log.e("EncodeThread", "encode error=$e") + } + } + isVideoEncoding = true + encodeThread?.start() + } + fun onActivityResult(resultCode: Int, data: Intent) { mediaProjection = mediaProjectionManager?.getMediaProjection(resultCode, data) if (type == TYPE_SCREEN_SHOT) { getBitmap() + } else if (type == TYPE_SCREEN_LIVING) { + initMediaCodec(displayMetrics!!.widthPixels, displayMetrics!!.heightPixels) + startVideoEncoder() } } @@ -110,6 +176,7 @@ open class MediaProjectionController(type: Int) { fun stopScreenRecord() { mediaProjection?.stop() virtualDisplay?.release() + videoEncoder?.release() } } \ No newline at end of file diff --git a/AndroidMedia/src/main/java/com/frank/androidmedia/listener/VideoEncodeCallback.kt b/AndroidMedia/src/main/java/com/frank/androidmedia/listener/VideoEncodeCallback.kt new file mode 100644 index 0000000..a3da3f1 --- /dev/null +++ b/AndroidMedia/src/main/java/com/frank/androidmedia/listener/VideoEncodeCallback.kt @@ -0,0 +1,12 @@ +package com.frank.androidmedia.listener + +/** + * @author xufulong + * @date 4/1/22 1:44 PM + * @desc + */ +interface VideoEncodeCallback { + + fun onVideoEncodeData(data: ByteArray, size: Int, flag: Int, timestamp: Long) + +} \ No newline at end of file