mirror of
https://github.com/rustdesk/rustdesk.git
synced 2024-12-03 19:39:20 +08:00
improve android server performance
This commit is contained in:
parent
1f4610a3d0
commit
d054da3404
@ -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))
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user