Feature: add screen living stream

pull/209/head
xufuji456 3 years ago
parent 54d7f094bc
commit 6644456a95
  1. 67
      AndroidMedia/src/main/java/com/frank/androidmedia/controller/MediaProjectionController.kt
  2. 12
      AndroidMedia/src/main/java/com/frank/androidmedia/listener/VideoEncodeCallback.kt

@ -8,12 +8,17 @@ import android.graphics.PixelFormat
import android.hardware.display.DisplayManager import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay import android.hardware.display.VirtualDisplay
import android.media.ImageReader 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.MediaProjection
import android.media.projection.MediaProjectionManager import android.media.projection.MediaProjectionManager
import android.os.Environment import android.os.Environment
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.Log
import android.view.Surface import android.view.Surface
import android.view.WindowManager import android.view.WindowManager
import com.frank.androidmedia.listener.VideoEncodeCallback
import java.io.FileOutputStream import java.io.FileOutputStream
import java.lang.Exception import java.lang.Exception
@ -39,10 +44,20 @@ open class MediaProjectionController(type: Int) {
private var mediaProjection: MediaProjection? = null private var mediaProjection: MediaProjection? = null
private var mediaProjectionManager: MediaProjectionManager? = 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 { init {
this.type = type this.type = type
} }
fun setVideoEncodeListener(encodeCallback: VideoEncodeCallback) {
videoEncodeCallback = encodeCallback
}
fun startScreenRecord(context: Context) { fun startScreenRecord(context: Context) {
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
displayMetrics = DisplayMetrics() displayMetrics = DisplayMetrics()
@ -95,10 +110,61 @@ open class MediaProjectionController(type: Int) {
}, null) }, 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) { fun onActivityResult(resultCode: Int, data: Intent) {
mediaProjection = mediaProjectionManager?.getMediaProjection(resultCode, data) mediaProjection = mediaProjectionManager?.getMediaProjection(resultCode, data)
if (type == TYPE_SCREEN_SHOT) { if (type == TYPE_SCREEN_SHOT) {
getBitmap() getBitmap()
} else if (type == TYPE_SCREEN_LIVING) {
initMediaCodec(displayMetrics!!.widthPixels, displayMetrics!!.heightPixels)
startVideoEncoder()
} }
} }
@ -110,6 +176,7 @@ open class MediaProjectionController(type: Int) {
fun stopScreenRecord() { fun stopScreenRecord() {
mediaProjection?.stop() mediaProjection?.stop()
virtualDisplay?.release() virtualDisplay?.release()
videoEncoder?.release()
} }
} }

@ -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)
}
Loading…
Cancel
Save