package com.runt.live.media
|
|
import android.content.res.Configuration
|
import android.graphics.Bitmap
|
import android.graphics.PixelFormat
|
import android.graphics.Point
|
import android.graphics.Rect
|
import android.hardware.display.DisplayManager
|
import android.hardware.display.VirtualDisplay
|
import android.media.ImageReader
|
import android.media.projection.MediaProjection
|
import android.os.Handler
|
import android.os.Looper
|
import android.util.Log
|
import com.runt.live.cpp.LiveMiniView
|
import com.runt.live.cpp.LiveMiniView.mainStreamCode
|
import com.runt.live.data.StreamWindow
|
import com.runt.live.enum.LiveState
|
import com.runt.open.mvi.OpenApplication
|
import java.nio.ByteBuffer
|
import java.util.Date
|
import java.util.concurrent.Executors
|
import java.util.concurrent.TimeUnit
|
import kotlin.concurrent.thread
|
|
|
/**
|
* @author Runt(qingingrunt2010@qq.com)
|
* @purpose 屏幕
|
* @date 9/22/24
|
*/
|
class ScreenHelper {
|
|
private var width = 0 ;
|
private var height = 0;
|
private var imageReader:ImageReader? = null;
|
private var virtualDisplay:VirtualDisplay? = null;
|
private var streamWindow:StreamWindow ? = null;
|
private var mediaProjection : MediaProjection? = null;
|
private var time = Date().time;
|
private var callBack = object : MediaProjection.Callback() {
|
override fun onStop() {
|
super.onStop()
|
|
}
|
}
|
|
fun initRecorder( width : Int , height : Int , dpi : Int , mediaProjection : MediaProjection ) {
|
this.width = width;
|
this.height = height;
|
this.imageReader = ImageReader.newInstance(width,height, PixelFormat.RGBA_8888,2)
|
this.mediaProjection = mediaProjection;
|
mediaProjection.registerCallback(callBack, Handler(Looper.getMainLooper()))
|
this.virtualDisplay = mediaProjection.createVirtualDisplay("ScreenCapture" , width , height , dpi , DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR , imageReader!!.surface , null , null)
|
}
|
fun openRecorder( streamWindow : StreamWindow ) {
|
this.streamWindow = streamWindow
|
streamWindow.sizeState.value = Point(width, height)
|
startReadingFrames()
|
}
|
|
private fun startReadingFrames() {
|
|
imageReader !!.setOnImageAvailableListener({ imageReader ->
|
val image = imageReader!!.acquireLatestImage()
|
if (image != null) { // 处理图像
|
if(Date().time - time < 1000/30){
|
image.close();
|
return@setOnImageAvailableListener;
|
}
|
time = Date().time
|
var plane = image.planes[0];
|
var buffer = plane.buffer;
|
var l = buffer.remaining();
|
var bytes = ByteArray(l);
|
var width = image.getWidth();
|
var height = image.getHeight();
|
var pixelStride = plane.pixelStride;
|
var rowStride = plane.rowStride;
|
val padding = rowStride - pixelStride * width
|
//实际宽度
|
val newWidth = width + padding / pixelStride;
|
//Log.i("11111" , "startReadingFrames: padding:${padding} width:${width} height:${height}")
|
buffer.get(bytes)
|
buffer.clear()
|
//printFirstRowRGBA(bytes,width/2)
|
//printFirstRowRGBA(bytes,width/8)
|
image.close()
|
var cropX = 0 ;
|
var cropY = 0;
|
if( streamWindow!!.remark!!.toInt() == Configuration.ORIENTATION_LANDSCAPE ){
|
cropY = 0 ;
|
}else{
|
cropX = (newWidth - width) / 2 ;
|
}
|
if(width != this.width || height != this.height){
|
streamWindow!!.sizeState.value = Point(width,height)
|
}
|
//Log.i("222222" , "startReadingFrames: ${bytes.size} newWidth:${newWidth} ${width} ${height} crop=${cropX}x${cropY} ${this.width}x${this.height}")
|
bytes = cropRGBA(bytes,newWidth, cropX ,cropY, width ,height)
|
thread {
|
if(streamWindow!!.videoState.value == LiveState.IN_LIVE || streamWindow!!.id == mainStreamCode){
|
var t = Date().time;
|
swapRBMultiThread(bytes,width,height);
|
LiveMiniView.native_push_rgba(streamWindow!!.id,bytes)
|
Log.i("222222" , "native_push_rgba: ${Date().time - t}")
|
}
|
}
|
thread {
|
if(OpenApplication.getApplication().isInfront()){
|
// 将 Image 转化为 Bitmap 对象
|
var t = Date().time;
|
val bitmap = Bitmap.createBitmap(width , height , Bitmap.Config.ARGB_8888)
|
// 直接通过 copy 方法将 RGBA 数据复制到 Bitmap 的像素中
|
val buffer1 = ByteBuffer.wrap(bytes)
|
buffer1.rewind()
|
bitmap.copyPixelsFromBuffer(buffer1)
|
//Log.i("222222" , "startReadingFrames: ${width} ${height}")
|
streamWindow?.surfaceHolder?.let {
|
var c = it.lockCanvas();
|
if (c != null) {
|
var src = Rect(0 , 0 , bitmap.width , bitmap.height)
|
var des = Rect(0 , 0 , it.surfaceFrame.width() , it.surfaceFrame.height())
|
c.drawBitmap(bitmap , src , des , null)
|
it.unlockCanvasAndPost(c)
|
}
|
}
|
bitmap.recycle();
|
//Log.i("222222" , "Canvasbitmap: ${Date().time - t}")
|
}
|
}
|
}
|
} , null)
|
}
|
|
fun swapRBMultiThread(rgbaData : ByteArray , width : Int , height : Int) {
|
val numThreads = Runtime.getRuntime().availableProcessors()
|
val executor = Executors.newFixedThreadPool(numThreads)
|
val chunkSize = height / numThreads
|
|
for (t in 0 until numThreads) {
|
val startRow = t * chunkSize
|
val endRow = if ((t == numThreads - 1)) height else startRow + chunkSize
|
|
executor.submit {
|
for (row in startRow until endRow) {
|
val rowStart = row * width * 4 // 每行开始的位置
|
var i = rowStart
|
while (i < rowStart + width * 4) {
|
val temp = rgbaData[i]
|
rgbaData[i] = rgbaData[i + 2]
|
rgbaData[i + 2] = temp
|
i += 4
|
}
|
}
|
}
|
}
|
executor.shutdown()
|
try {
|
executor.awaitTermination(Long.MAX_VALUE , TimeUnit.MILLISECONDS)
|
} catch (e : InterruptedException) {
|
e.printStackTrace()
|
}
|
//Log.i("22222" , "swapRBMultiThread: ${numThreads}")
|
}
|
|
private fun printFirstRowRGBA(rgbaData : ByteArray , width : Int) { // 每行的字节数(宽度 × 每像素字节数)
|
val rowBytes = width * 4
|
|
print("First row RGBA values: ")
|
var i = 0
|
while (i < rowBytes) { // 每像素 4 字节(R, G, B, A)
|
val r = rgbaData[i].toInt() and 0xFF // Red
|
val g = rgbaData[i + 1].toInt() and 0xFF // Green
|
val b = rgbaData[i + 2].toInt() and 0xFF // Blue
|
val a = rgbaData[i + 3].toInt() and 0xFF // Alpha
|
System.out.printf("Pixel[%d]: R=%d, G=%d, B=%d, A=%d | " , i / 4 , r , g , b , a)
|
i += 4
|
System.out.println()
|
}
|
println()
|
}
|
private fun cropRGBA(srcData : ByteArray? , srcWidth : Int , cropX : Int , cropY : Int , cropWidth : Int , cropHeight : Int) : ByteArray { // 每个像素占 4 个字节(RGBA)
|
val pixelStride = 4
|
|
// 创建目标数组
|
val croppedData = ByteArray(cropWidth * cropHeight * pixelStride)
|
|
// 循环裁剪每一行
|
for (y in 0 until cropHeight) { // 计算源数组的起始位置
|
val srcOffset = ((cropY + y) * srcWidth + cropX) * pixelStride
|
|
// 计算目标数组的起始位置
|
val destOffset = y * cropWidth * pixelStride
|
|
// 将一行数据复制到目标数组
|
System.arraycopy(srcData , srcOffset , croppedData , destOffset , cropWidth * pixelStride)
|
}
|
|
return croppedData
|
}
|
|
fun releaseDisplay(){
|
virtualDisplay?.release()
|
}
|
|
fun stopRecord(){
|
mediaProjection?.stop();
|
releaseDisplay();
|
virtualDisplay = null;
|
}
|
|
fun getState() : Int{
|
if(virtualDisplay == null){
|
return -1;
|
}
|
return 1;
|
}
|
}
|