improve android server performance

This commit is contained in:
csf 2022-04-09 21:38:46 +08:00
parent 1f4610a3d0
commit d054da3404
4 changed files with 100 additions and 89 deletions

View File

@ -17,7 +17,6 @@ import android.graphics.PixelFormat
import android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC import android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
import android.hardware.display.VirtualDisplay import android.hardware.display.VirtualDisplay
import android.media.* import android.media.*
import android.media.AudioRecord.READ_BLOCKING
import android.media.projection.MediaProjection import android.media.projection.MediaProjection
import android.media.projection.MediaProjectionManager import android.media.projection.MediaProjectionManager
import android.os.* import android.os.*
@ -33,6 +32,7 @@ import java.util.concurrent.Executors
import kotlin.concurrent.thread import kotlin.concurrent.thread
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import java.nio.ByteBuffer
const val EXTRA_MP_DATA = "mp_intent" const val EXTRA_MP_DATA = "mp_intent"
const val INIT_SERVICE = "init_service" const val INIT_SERVICE = "init_service"
@ -42,14 +42,14 @@ const val EXTRA_LOGIN_REQ_NOTIFY = "EXTRA_LOGIN_REQ_NOTIFY"
const val DEFAULT_NOTIFY_TITLE = "RustDesk" const val DEFAULT_NOTIFY_TITLE = "RustDesk"
const val DEFAULT_NOTIFY_TEXT = "Service is running" const val DEFAULT_NOTIFY_TEXT = "Service is running"
const val DEFAULT_NOTIFY_ID = 1 const val DEFAULT_NOTIFY_ID = 1
const val NOTIFY_ID_OFFSET = 100 const val NOTIFY_ID_OFFSET = 100
const val NOTIFY_TYPE_START_CAPTURE = "NOTIFY_TYPE_START_CAPTURE" const val NOTIFY_TYPE_START_CAPTURE = "NOTIFY_TYPE_START_CAPTURE"
const val MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_VP9 const val MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_VP9
// video const // video const
const val MAX_SCREEN_SIZE = 1200 const val MAX_SCREEN_SIZE = 1200
const val VIDEO_KEY_BIT_RATE = 1024_000 const val VIDEO_KEY_BIT_RATE = 1024_000
const val VIDEO_KEY_FRAME_RATE = 30 const val VIDEO_KEY_FRAME_RATE = 30
@ -65,33 +65,6 @@ class MainService : Service() {
System.loadLibrary("rustdesk") System.loadLibrary("rustdesk")
} }
// rust call jvm
@Keep
fun rustGetVideoRaw(): ByteArray {
return if (videoData != null) {
videoData!!
} else {
videoZeroData
}
}
@Keep
fun rustGetAudioRaw(): FloatArray {
return if (isNewData && audioData != null) {
isNewData = false
audioData!!
} else {
audioZeroData
}
}
@Keep
fun rustGetAudioRawLen(): Int {
return if (isNewData && audioData != null && audioData!!.isNotEmpty()) {
audioData!!.size
} else 0
}
@Keep @Keep
fun rustGetByName(name: String): String { fun rustGetByName(name: String): String {
return when (name) { return when (name) {
@ -109,13 +82,13 @@ class MainService : Service() {
val id = jsonObject["id"] as Int val id = jsonObject["id"] as Int
val username = jsonObject["name"] as String val username = jsonObject["name"] as String
val peerId = jsonObject["peer_id"] as String val peerId = jsonObject["peer_id"] as String
val type = if (jsonObject["is_file_transfer"] as Boolean){ val type = if (jsonObject["is_file_transfer"] as Boolean) {
translate("File Connection") translate("File Connection")
}else{ } else {
translate("Screen Connection") translate("Screen Connection")
} }
loginRequestNotification(id,type,username,peerId) loginRequestNotification(id, type, username, peerId)
}catch (e:JSONException){ } catch (e: JSONException) {
e.printStackTrace() e.printStackTrace()
} }
} }
@ -127,16 +100,16 @@ class MainService : Service() {
val username = jsonObject["name"] as String val username = jsonObject["name"] as String
val peerId = jsonObject["peer_id"] as String val peerId = jsonObject["peer_id"] as String
val isFileTransfer = jsonObject["is_file_transfer"] as Boolean val isFileTransfer = jsonObject["is_file_transfer"] as Boolean
val type = if (isFileTransfer){ val type = if (isFileTransfer) {
translate("File Connection") translate("File Connection")
}else{ } else {
translate("Screen Connection") translate("Screen Connection")
} }
if(!isFileTransfer && !isStart){ if (!isFileTransfer && !isStart) {
startCapture() startCapture()
} }
onClientAuthorizedNotification(id,type,username,peerId) onClientAuthorizedNotification(id, type, username, peerId)
}catch (e:JSONException){ } catch (e: JSONException) {
e.printStackTrace() e.printStackTrace()
} }
@ -145,46 +118,46 @@ class MainService : Service() {
Log.d(logTag, "from rust:stop_capture") Log.d(logTag, "from rust:stop_capture")
stopCapture() stopCapture()
} }
else -> {} else -> {
}
} }
} }
// jvm call rust // jvm call rust
private external fun init(ctx: Context) private external fun init(ctx: Context)
private external fun startServer() private external fun startServer()
private external fun translateLocale(localeName:String,input: String) : String private external fun onVideoFrameUpdate(buf: ByteBuffer)
private external fun onAudioFrameUpdate(buf: ByteBuffer)
private external fun translateLocale(localeName: String, input: String): String
// private external fun sendVp9(data: ByteArray) // private external fun sendVp9(data: ByteArray)
private fun translate(input:String):String{ private fun translate(input: String): String {
Log.d(logTag,"translate:$LOCAL_NAME") Log.d(logTag, "translate:$LOCAL_NAME")
return translateLocale(LOCAL_NAME,input) return translateLocale(LOCAL_NAME, input)
} }
private val logTag = "LOG_SERVICE" private val logTag = "LOG_SERVICE"
private val useVP9 = false private val useVP9 = false
private val binder = LocalBinder() private val binder = LocalBinder()
private var _isReady = false private var _isReady = false
private var _isStart = false private var _isStart = false
val isReady: Boolean val isReady: Boolean
get() = _isReady get() = _isReady
val isStart: Boolean val isStart: Boolean
get() = _isStart get() = _isStart
// video
private var mediaProjection: MediaProjection? = null private var mediaProjection: MediaProjection? = null
private var surface: Surface? = null private var surface: Surface? = null
private val sendVP9Thread = Executors.newSingleThreadExecutor() private val sendVP9Thread = Executors.newSingleThreadExecutor()
private var videoEncoder: MediaCodec? = null private var videoEncoder: MediaCodec? = null
private var videoData: ByteArray? = null
private var imageReader: ImageReader? = null private var imageReader: ImageReader? = null
private val videoZeroData = ByteArray(32)
private var virtualDisplay: VirtualDisplay? = null private var virtualDisplay: VirtualDisplay? = null
// audio // audio
private var audioRecorder: AudioRecord? = null private var audioRecorder: AudioRecord? = null
private var audioData: FloatArray? = null private var audioReader: AudioReader? = null
private var minBufferSize = 0 private var minBufferSize = 0
private var isNewData = false
private val audioZeroData: FloatArray = FloatArray(32)
private var audioRecordStat = false private var audioRecordStat = false
// notification // notification
@ -239,13 +212,13 @@ class MainService : Service() {
// TODO // TODO
null null
} else { } else {
Log.d(logTag,"ImageReader.newInstance:INFO:$INFO") Log.d(logTag, "ImageReader.newInstance:INFO:$INFO")
imageReader = imageReader =
ImageReader.newInstance( ImageReader.newInstance(
INFO.screenWidth, INFO.screenWidth,
INFO.screenHeight, INFO.screenHeight,
PixelFormat.RGBA_8888, PixelFormat.RGBA_8888,
2 4
).apply { ).apply {
setOnImageAvailableListener({ imageReader: ImageReader -> setOnImageAvailableListener({ imageReader: ImageReader ->
try { try {
@ -254,20 +227,10 @@ class MainService : Service() {
val planes = image.planes val planes = image.planes
val buffer = planes[0].buffer val buffer = planes[0].buffer
buffer.rewind() buffer.rewind()
// Be careful about OOM! onVideoFrameUpdate(buffer)
if (videoData == null) {
videoData = ByteArray(buffer.capacity())
buffer.get(videoData!!)
Log.d(logTag, "init video ${videoData!!.size}")
} else {
buffer.get(videoData!!)
}
} }
} catch (ignored: java.lang.Exception) { } catch (ignored: java.lang.Exception) {
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
imageReader.discardFreeBuffers()
}
}, null) }, null)
} }
Log.d(logTag, "ImageReader.setOnImageAvailableListener done") Log.d(logTag, "ImageReader.setOnImageAvailableListener done")
@ -276,7 +239,7 @@ class MainService : Service() {
} }
fun startCapture(): Boolean { fun startCapture(): Boolean {
if (isStart){ if (isStart) {
return true return true
} }
if (mediaProjection == null) { if (mediaProjection == null) {
@ -311,7 +274,6 @@ class MainService : Service() {
} }
virtualDisplay = null virtualDisplay = null
videoEncoder = null videoEncoder = null
videoData = null
// release audio // release audio
audioRecordStat = false audioRecordStat = false
@ -330,7 +292,7 @@ class MainService : Service() {
mediaProjection = null mediaProjection = null
checkMediaPermission() checkMediaPermission()
stopService(Intent(this,InputService::class.java)) // close input service maybe not work stopService(Intent(this, InputService::class.java)) // close input service maybe not work
stopForeground(true) stopForeground(true)
stopSelf() stopSelf()
} }
@ -348,7 +310,7 @@ class MainService : Service() {
@SuppressLint("WrongConstant") @SuppressLint("WrongConstant")
private fun startRawVideoRecorder(mp: MediaProjection) { private fun startRawVideoRecorder(mp: MediaProjection) {
Log.d(logTag, "startRawVideoRecorder,screen info:$INFO") Log.d(logTag, "startRawVideoRecorder,screen info:$INFO")
if(surface==null){ if (surface == null) {
Log.d(logTag, "startRawVideoRecorder failed,surface is null") Log.d(logTag, "startRawVideoRecorder failed,surface is null")
return return
} }
@ -370,7 +332,7 @@ class MainService : Service() {
it.setCallback(cb) it.setCallback(cb)
it.start() it.start()
virtualDisplay = mp.createVirtualDisplay( virtualDisplay = mp.createVirtualDisplay(
"rustdesk test", "RustDeskVD",
INFO.screenWidth, INFO.screenHeight, 200, VIRTUAL_DISPLAY_FLAG_PUBLIC, INFO.screenWidth, INFO.screenHeight, 200, VIRTUAL_DISPLAY_FLAG_PUBLIC,
surface, null, null surface, null, null
) )
@ -423,14 +385,13 @@ class MainService : Service() {
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
private fun startAudioRecorder() { private fun startAudioRecorder() {
checkAudioRecorder() checkAudioRecorder()
if (audioData != null && audioRecorder != null && minBufferSize != 0) { if (audioReader != null && audioRecorder != null && minBufferSize != 0) {
audioRecorder!!.startRecording() audioRecorder!!.startRecording()
audioRecordStat = true audioRecordStat = true
thread { thread {
while (audioRecordStat) { while (audioRecordStat) {
val res = audioRecorder!!.read(audioData!!, 0, minBufferSize, READ_BLOCKING) audioReader!!.readSync(audioRecorder!!)?.let {
if (res != AudioRecord.ERROR_INVALID_OPERATION) { onAudioFrameUpdate(it)
isNewData = true
} }
} }
Log.d(logTag, "Exit audio thread") Log.d(logTag, "Exit audio thread")
@ -442,10 +403,11 @@ class MainService : Service() {
@RequiresApi(Build.VERSION_CODES.M) @RequiresApi(Build.VERSION_CODES.M)
private fun checkAudioRecorder() { private fun checkAudioRecorder() {
if (audioData != null && audioRecorder != null && minBufferSize != 0) { if (audioRecorder != null && audioRecorder != null && minBufferSize != 0) {
return return
} }
minBufferSize = 2 * AudioRecord.getMinBufferSize( // read f32 to byte , length * 4
minBufferSize = 2 * 4 * AudioRecord.getMinBufferSize(
AUDIO_SAMPLE_RATE, AUDIO_SAMPLE_RATE,
AUDIO_CHANNEL_MASK, AUDIO_CHANNEL_MASK,
AUDIO_ENCODING AUDIO_ENCODING
@ -454,8 +416,8 @@ class MainService : Service() {
Log.d(logTag, "get min buffer size fail!") Log.d(logTag, "get min buffer size fail!")
return return
} }
audioData = FloatArray(minBufferSize) audioReader = AudioReader(minBufferSize, 4)
Log.d(logTag, "init audioData len:${audioData!!.size}") Log.d(logTag, "init audioData len:$minBufferSize")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mediaProjection?.let { mediaProjection?.let {
val apcc = AudioPlaybackCaptureConfiguration.Builder(it) val apcc = AudioPlaybackCaptureConfiguration.Builder(it)
@ -511,7 +473,7 @@ class MainService : Service() {
private fun createForegroundNotification() { private fun createForegroundNotification() {
val intent = Intent(this, MainActivity::class.java).apply { val intent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
action = Intent.ACTION_MAIN action = Intent.ACTION_MAIN
addCategory(Intent.CATEGORY_LAUNCHER) addCategory(Intent.CATEGORY_LAUNCHER)
putExtra("type", type) putExtra("type", type)
} }
@ -536,7 +498,12 @@ class MainService : Service() {
startForeground(DEFAULT_NOTIFY_ID, notification) startForeground(DEFAULT_NOTIFY_ID, notification)
} }
private fun loginRequestNotification(clientID:Int, type: String, username: String, peerId: String) { private fun loginRequestNotification(
clientID: Int,
type: String,
username: String,
peerId: String
) {
cancelNotification(clientID) cancelNotification(clientID)
val notification = notificationBuilder val notification = notificationBuilder
.setOngoing(false) .setOngoing(false)
@ -550,7 +517,12 @@ class MainService : Service() {
notificationManager.notify(getClientNotifyID(clientID), notification) notificationManager.notify(getClientNotifyID(clientID), notification)
} }
private fun onClientAuthorizedNotification(clientID: Int, type: String, username: String, peerId: String) { private fun onClientAuthorizedNotification(
clientID: Int,
type: String,
username: String,
peerId: String
) {
cancelNotification(clientID) cancelNotification(clientID)
val notification = notificationBuilder val notification = notificationBuilder
.setOngoing(false) .setOngoing(false)
@ -561,11 +533,11 @@ class MainService : Service() {
notificationManager.notify(getClientNotifyID(clientID), notification) notificationManager.notify(getClientNotifyID(clientID), notification)
} }
private fun getClientNotifyID(clientID:Int):Int{ private fun getClientNotifyID(clientID: Int): Int {
return clientID + NOTIFY_ID_OFFSET return clientID + NOTIFY_ID_OFFSET
} }
fun cancelNotification(clientID:Int){ fun cancelNotification(clientID: Int) {
notificationManager.cancel(getClientNotifyID(clientID)) notificationManager.cancel(getClientNotifyID(clientID))
} }

View File

@ -2,6 +2,8 @@ package com.carriez.flutter_hbb
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.media.AudioRecord
import android.media.AudioRecord.READ_BLOCKING
import android.media.MediaCodecList import android.media.MediaCodecList
import android.media.MediaFormat import android.media.MediaFormat
import android.os.Build import android.os.Build
@ -11,6 +13,7 @@ import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import com.hjq.permissions.Permission import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions import com.hjq.permissions.XXPermissions
import java.nio.ByteBuffer
import java.util.* import java.util.*
@SuppressLint("ConstantLocale") @SuppressLint("ConstantLocale")
@ -25,7 +28,7 @@ data class Info(
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun testVP9Support(): Boolean { fun testVP9Support(): Boolean {
return true return true
val res = MediaCodecList(MediaCodecList.ALL_CODECS) val res = MediaCodecList(MediaCodecList.ALL_CODECS)
.findEncoderForFormat( .findEncoderForFormat(
MediaFormat.createVideoFormat( MediaFormat.createVideoFormat(
@ -37,7 +40,7 @@ fun testVP9Support(): Boolean {
return res != null return res != null
} }
fun requestPermission(context: Context,type: String){ fun requestPermission(context: Context, type: String) {
val permission = when (type) { val permission = when (type) {
"audio" -> { "audio" -> {
Permission.RECORD_AUDIO Permission.RECORD_AUDIO
@ -64,7 +67,7 @@ fun requestPermission(context: Context,type: String){
} }
} }
fun checkPermission(context: Context,type: String): Boolean { fun checkPermission(context: Context, type: String): Boolean {
val permission = when (type) { val permission = when (type) {
"audio" -> { "audio" -> {
Permission.RECORD_AUDIO Permission.RECORD_AUDIO
@ -76,5 +79,41 @@ fun checkPermission(context: Context,type: String): Boolean {
return false return false
} }
} }
return XXPermissions.isGranted(context,permission) return XXPermissions.isGranted(context, permission)
}
class AudioReader(val bufSize: Int, private val maxFrames: Int) {
private var currentPos = 0
private val bufferPool: Array<ByteBuffer>
init {
if (maxFrames < 0 || maxFrames > 32) {
throw Exception("Out of bounds")
}
if (bufSize <= 0) {
throw Exception("Wrong bufSize")
}
bufferPool = Array(maxFrames) {
ByteBuffer.allocateDirect(bufSize)
}
}
private fun next() {
currentPos++
if (currentPos >= maxFrames) {
currentPos = 0
}
}
@RequiresApi(Build.VERSION_CODES.M)
fun readSync(audioRecord: AudioRecord): ByteBuffer? {
val buffer = bufferPool[currentPos]
val res = audioRecord.read(buffer, bufSize, READ_BLOCKING)
return if (res > 0) {
next()
buffer
} else {
null
}
}
} }

View File

@ -1,4 +1,4 @@
<resources> <resources>
<string name="app_name">RustDesk</string> <string name="app_name">RustDesk</string>
<string name="accessibility_service_description">测试服务 输入服务</string> <string name="accessibility_service_description">Allow other devices to control your phone using virtual touch, when RustDesk screen sharing is established</string>
</resources> </resources>

View File

@ -41,14 +41,14 @@ class ServerModel with ChangeNotifier {
/** /**
* 1. check android permission * 1. check android permission
* 2. check config * 2. check config
* audio true by default (if permission on) * audio true by default (if permission on) (false default < Android 10)
* file true by default (if permission on) * file true by default (if permission on)
* input false by default (it need turning on manually everytime) * input false by default (it need turning on manually everytime)
*/ */
await Future.delayed(Duration(seconds: 1)); await Future.delayed(Duration(seconds: 1));
// audio // audio
if(!await PermissionManager.check("audio")){ if(androidVersion<30 || !await PermissionManager.check("audio")){
_audioOk = false; _audioOk = false;
FFI.setByName('option', jsonEncode( FFI.setByName('option', jsonEncode(
Map() Map()