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

View File

@ -2,6 +2,8 @@ package com.carriez.flutter_hbb
import android.annotation.SuppressLint
import android.content.Context
import android.media.AudioRecord
import android.media.AudioRecord.READ_BLOCKING
import android.media.MediaCodecList
import android.media.MediaFormat
import android.os.Build
@ -11,6 +13,7 @@ import android.util.Log
import androidx.annotation.RequiresApi
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
import java.nio.ByteBuffer
import java.util.*
@SuppressLint("ConstantLocale")
@ -25,7 +28,7 @@ data class Info(
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun testVP9Support(): Boolean {
return true
return true
val res = MediaCodecList(MediaCodecList.ALL_CODECS)
.findEncoderForFormat(
MediaFormat.createVideoFormat(
@ -37,7 +40,7 @@ fun testVP9Support(): Boolean {
return res != null
}
fun requestPermission(context: Context,type: String){
fun requestPermission(context: Context, type: String) {
val permission = when (type) {
"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) {
"audio" -> {
Permission.RECORD_AUDIO
@ -76,5 +79,41 @@ fun checkPermission(context: Context,type: String): Boolean {
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>
<string name="app_name">RustDesk</string>
<string name="accessibility_service_description">测试服务 输入服务</string>
</resources>
<string name="accessibility_service_description">Allow other devices to control your phone using virtual touch, when RustDesk screen sharing is established</string>
</resources>

View File

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