From 5ef9983f959894c02fab34a2793ae4d5142c026d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Mon, 28 Dec 2020 22:30:11 +0100 Subject: [PATCH] Finish PS5 on Android --- android/app/src/main/cpp/chiaki-jni.c | 37 ++++++++++++------ android/app/src/main/cpp/video-decoder.c | 21 +++++++++- android/app/src/main/cpp/video-decoder.h | 4 +- .../com/metallic/chiaki/common/DisplayHost.kt | 3 ++ .../com/metallic/chiaki/common/Preferences.kt | 20 +++++++++- .../chiaki/common/SerializedSettings.kt | 2 +- .../chiaki/discovery/DiscoveryManager.kt | 2 +- .../java/com/metallic/chiaki/lib/Chiaki.kt | 19 +++++++-- .../main/DisplayHostRecyclerViewAdapter.kt | 17 ++++---- .../com/metallic/chiaki/main/MainActivity.kt | 2 +- .../com/metallic/chiaki/main/MainViewModel.kt | 4 +- .../metallic/chiaki/regist/RegistActivity.kt | 39 ++++++++++--------- .../metallic/chiaki/regist/RegistViewModel.kt | 11 +++--- .../chiaki/settings/SettingsFragment.kt | 12 ++++++ .../app/src/main/res/drawable/ic_codec.xml | 15 +++++++ .../src/main/res/drawable/ic_console_ps5.xml | 9 +++++ .../res/drawable/ic_console_ps5_ready.xml | 16 ++++++++ .../res/drawable/ic_console_ps5_standby.xml | 16 ++++++++ .../src/main/res/layout/activity_regist.xml | 12 +++++- .../src/main/res/layout/item_display_host.xml | 7 ++-- android/app/src/main/res/values/strings.xml | 5 +++ android/app/src/main/res/xml/preferences.xml | 6 +++ 22 files changed, 219 insertions(+), 60 deletions(-) create mode 100644 android/app/src/main/res/drawable/ic_codec.xml create mode 100644 android/app/src/main/res/drawable/ic_console_ps5.xml create mode 100644 android/app/src/main/res/drawable/ic_console_ps5_ready.xml create mode 100644 android/app/src/main/res/drawable/ic_console_ps5_standby.xml diff --git a/android/app/src/main/cpp/chiaki-jni.c b/android/app/src/main/cpp/chiaki-jni.c index a65c2e3..35543ad 100644 --- a/android/app/src/main/cpp/chiaki-jni.c +++ b/android/app/src/main/cpp/chiaki-jni.c @@ -115,13 +115,13 @@ JNIEXPORT jboolean JNICALL JNI_FCN(quitReasonIsStopped)(JNIEnv *env, jobject obj return value == CHIAKI_QUIT_REASON_STOPPED; } -JNIEXPORT jobject JNICALL JNI_FCN(videoProfilePreset)(JNIEnv *env, jobject obj, jint resolution_preset, jint fps_preset) +JNIEXPORT jobject JNICALL JNI_FCN(videoProfilePreset)(JNIEnv *env, jobject obj, jint resolution_preset, jint fps_preset, jobject codec) { ChiakiConnectVideoProfile profile = { 0 }; chiaki_connect_video_profile_preset(&profile, (ChiakiVideoResolutionPreset)resolution_preset, (ChiakiVideoFPSPreset)fps_preset); jclass profile_class = E->FindClass(env, BASE_PACKAGE"/ConnectVideoProfile"); - jmethodID profile_ctor = E->GetMethodID(env, profile_class, "", "(IIII)V"); - return E->NewObject(env, profile_class, profile_ctor, profile.width, profile.height, profile.max_fps, profile.bitrate); + jmethodID profile_ctor = E->GetMethodID(env, profile_class, "", "(IIIIL"BASE_PACKAGE"/Codec;)V"); + return E->NewObject(env, profile_class, profile_ctor, profile.width, profile.height, profile.max_fps, profile.bitrate, codec); } typedef struct android_chiaki_session_t @@ -198,6 +198,7 @@ JNIEXPORT void JNICALL JNI_FCN(sessionCreate)(JNIEnv *env, jobject obj, jobject jclass result_class = E->GetObjectClass(env, result); jclass connect_info_class = E->GetObjectClass(env, connect_info_obj); + jboolean ps5 = E->GetBooleanField(env, connect_info_obj, E->GetFieldID(env, connect_info_class, "ps5", "Z")); jstring host_string = E->GetObjectField(env, connect_info_obj, E->GetFieldID(env, connect_info_class, "host", "Ljava/lang/String;")); jbyteArray regist_key_array = E->GetObjectField(env, connect_info_obj, E->GetFieldID(env, connect_info_class, "registKey", "[B")); jbyteArray morning_array = E->GetObjectField(env, connect_info_obj, E->GetFieldID(env, connect_info_class, "morning", "[B")); @@ -205,6 +206,8 @@ JNIEXPORT void JNICALL JNI_FCN(sessionCreate)(JNIEnv *env, jobject obj, jobject jclass connect_video_profile_class = E->GetObjectClass(env, connect_video_profile_obj); ChiakiConnectInfo connect_info = { 0 }; + connect_info.ps5 = ps5; + const char *str_borrow = E->GetStringUTFChars(env, host_string, NULL); connect_info.host = host_str = strdup(str_borrow); E->ReleaseStringUTFChars(env, host_string, str_borrow); @@ -239,6 +242,13 @@ JNIEXPORT void JNICALL JNI_FCN(sessionCreate)(JNIEnv *env, jobject obj, jobject connect_info.video_profile.max_fps = (unsigned int)E->GetIntField(env, connect_video_profile_obj, E->GetFieldID(env, connect_video_profile_class, "maxFPS", "I")); connect_info.video_profile.bitrate = (unsigned int)E->GetIntField(env, connect_video_profile_obj, E->GetFieldID(env, connect_video_profile_class, "bitrate", "I")); + jobject codec_obj = E->GetObjectField(env, connect_video_profile_obj, E->GetFieldID(env, connect_video_profile_class, "codec", "L"BASE_PACKAGE"/Codec;")); + jclass codec_class = E->GetObjectClass(env, codec_obj); + jint target_value = E->GetIntField(env, codec_obj, E->GetFieldID(env, codec_class, "value", "I")); + connect_info.video_profile.codec = (ChiakiCodec)target_value; + + connect_info.video_profile_auto_downgrade = true; + session = CHIAKI_NEW(AndroidChiakiSession); if(!session) { @@ -247,7 +257,8 @@ JNIEXPORT void JNICALL JNI_FCN(sessionCreate)(JNIEnv *env, jobject obj, jobject } memset(session, 0, sizeof(AndroidChiakiSession)); session->log = log; - err = android_chiaki_video_decoder_init(&session->video_decoder, log, connect_info.video_profile.width, connect_info.video_profile.height); + err = android_chiaki_video_decoder_init(&session->video_decoder, log, connect_info.video_profile.width, connect_info.video_profile.height, + connect_info.ps5 ? connect_info.video_profile.codec : CHIAKI_CODEC_H264); if(err != CHIAKI_ERR_SUCCESS) { free(session); @@ -592,6 +603,8 @@ typedef struct android_chiaki_regist_t jobject java_regist; jmethodID java_regist_event_meth; + jclass java_target_class; + jobject java_regist_event_canceled; jobject java_regist_event_failed; jclass java_regist_event_success_class; @@ -601,11 +614,10 @@ typedef struct android_chiaki_regist_t jmethodID java_regist_host_ctor; } AndroidChiakiRegist; -static jobject create_jni_target(JNIEnv *env, ChiakiTarget target) +static jobject create_jni_target(JNIEnv *env, jclass target_class, ChiakiTarget target) { - jclass cls = E->FindClass(env, BASE_PACKAGE"/Target"); - jmethodID meth = E->GetStaticMethodID(env, cls, "fromValue", "(I)L"BASE_PACKAGE"/Target;"); - return E->CallStaticObjectMethod(env, cls, meth, (jint)target); + jmethodID meth = E->GetStaticMethodID(env, target_class, "fromValue", "(I)L"BASE_PACKAGE"/Target;"); + return E->CallStaticObjectMethod(env, target_class, meth, (jint)target); } static void android_chiaki_regist_cb(ChiakiRegistEvent *event, void *user) @@ -629,7 +641,7 @@ static void android_chiaki_regist_cb(ChiakiRegistEvent *event, void *user) { ChiakiRegisteredHost *host = event->registered_host; jobject java_host = E->NewObject(env, regist->java_regist_host_class, regist->java_regist_host_ctor, - create_jni_target(env, host->target), + create_jni_target(env, regist->java_target_class, host->target), jnistr_from_ascii(env, host->ap_ssid), jnistr_from_ascii(env, host->ap_bssid), jnistr_from_ascii(env, host->ap_key), @@ -654,6 +666,7 @@ static void android_chiaki_regist_fini_partial(JNIEnv *env, AndroidChiakiRegist { android_chiaki_jni_log_fini(®ist->log, env); E->DeleteGlobalRef(env, regist->java_regist); + E->DeleteGlobalRef(env, regist->java_target_class); E->DeleteGlobalRef(env, regist->java_regist_event_canceled); E->DeleteGlobalRef(env, regist->java_regist_event_failed); E->DeleteGlobalRef(env, regist->java_regist_event_success_class); @@ -676,6 +689,8 @@ JNIEXPORT void JNICALL JNI_FCN(registStart)(JNIEnv *env, jobject obj, jobject re regist->java_regist = E->NewGlobalRef(env, java_regist); regist->java_regist_event_meth = E->GetMethodID(env, E->GetObjectClass(env, regist->java_regist), "event", "(L"BASE_PACKAGE"/RegistEvent;)V"); + regist->java_target_class = E->NewGlobalRef(env, E->FindClass(env, BASE_PACKAGE"/Target")); + regist->java_regist_event_canceled = E->NewGlobalRef(env, get_kotlin_global_object(env, BASE_PACKAGE"/RegistEventCanceled")); regist->java_regist_event_failed = E->NewGlobalRef(env, get_kotlin_global_object(env, BASE_PACKAGE"/RegistEventFailed")); regist->java_regist_event_success_class = E->NewGlobalRef(env, E->FindClass(env, BASE_PACKAGE"/RegistEventSuccess")); @@ -688,8 +703,8 @@ JNIEXPORT void JNICALL JNI_FCN(registStart)(JNIEnv *env, jobject obj, jobject re "Ljava/lang/String;" // apBssid: String "Ljava/lang/String;" // apKey: String "Ljava/lang/String;" // apName: String - "[B" // ps4Mac: ByteArray - "Ljava/lang/String;" // ps4Nickname: String + "[B" // serverMac: ByteArray + "Ljava/lang/String;" // serverNickname: String "[B" // rpRegistKey: ByteArray "I" // rpKeyType: UInt "[B" // rpKey: ByteArray diff --git a/android/app/src/main/cpp/video-decoder.c b/android/app/src/main/cpp/video-decoder.c index 43c9bc4..d57623d 100644 --- a/android/app/src/main/cpp/video-decoder.c +++ b/android/app/src/main/cpp/video-decoder.c @@ -14,13 +14,15 @@ static void *android_chiaki_video_decoder_output_thread_func(void *user); -ChiakiErrorCode android_chiaki_video_decoder_init(AndroidChiakiVideoDecoder *decoder, ChiakiLog *log, int32_t target_width, int32_t target_height) +ChiakiErrorCode android_chiaki_video_decoder_init(AndroidChiakiVideoDecoder *decoder, ChiakiLog *log, int32_t target_width, int32_t target_height, ChiakiCodec codec) { decoder->log = log; decoder->codec = NULL; decoder->timestamp_cur = 0; decoder->target_width = target_width; decoder->target_height = target_height; + decoder->target_codec = codec; + decoder->shutdown_output = false; return chiaki_mutex_init(&decoder->codec_mutex, false); } @@ -29,17 +31,20 @@ void android_chiaki_video_decoder_fini(AndroidChiakiVideoDecoder *decoder) if(decoder->codec) { chiaki_mutex_lock(&decoder->codec_mutex); + decoder->shutdown_output = true; ssize_t codec_buf_index = AMediaCodec_dequeueInputBuffer(decoder->codec, -1); if(codec_buf_index >= 0) { CHIAKI_LOGI(decoder->log, "Video Decoder sending EOS buffer"); AMediaCodec_queueInputBuffer(decoder->codec, (size_t)codec_buf_index, 0, 0, decoder->timestamp_cur++, AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM); + AMediaCodec_stop(decoder->codec); chiaki_mutex_unlock(&decoder->codec_mutex); chiaki_thread_join(&decoder->output_thread, NULL); } else { CHIAKI_LOGE(decoder->log, "Failed to get input buffer for shutting down Video Decoder!"); + AMediaCodec_stop(decoder->codec); chiaki_mutex_unlock(&decoder->codec_mutex); } AMediaCodec_delete(decoder->codec); @@ -67,7 +72,8 @@ void android_chiaki_video_decoder_set_surface(AndroidChiakiVideoDecoder *decoder decoder->window = ANativeWindow_fromSurface(env, surface); - const char *mime = "video/avc"; + const char *mime = chiaki_codec_is_h265(decoder->target_codec) ? "video/hevc" : "video/avc"; + CHIAKI_LOGI(decoder->log, "Initializing decoder with mime %s", mime); decoder->codec = AMediaCodec_createDecoderByType(mime); if(!decoder->codec) @@ -181,6 +187,17 @@ static void *android_chiaki_video_decoder_output_thread_func(void *user) break; } } + else + { + chiaki_mutex_lock(&decoder->codec_mutex); + bool shutdown = decoder->shutdown_output; + chiaki_mutex_unlock(&decoder->codec_mutex); + if(shutdown) + { + CHIAKI_LOGI(decoder->log, "Video Decoder Output Thread detected shutdown after reported error"); + break; + } + } } CHIAKI_LOGI(decoder->log, "Video Decoder Output Thread exiting"); diff --git a/android/app/src/main/cpp/video-decoder.h b/android/app/src/main/cpp/video-decoder.h index 0578e7f..b12ba85 100644 --- a/android/app/src/main/cpp/video-decoder.h +++ b/android/app/src/main/cpp/video-decoder.h @@ -19,11 +19,13 @@ typedef struct android_chiaki_video_decoder_t ANativeWindow *window; uint64_t timestamp_cur; ChiakiThread output_thread; + bool shutdown_output; int32_t target_width; int32_t target_height; + ChiakiCodec target_codec; } AndroidChiakiVideoDecoder; -ChiakiErrorCode android_chiaki_video_decoder_init(AndroidChiakiVideoDecoder *decoder, ChiakiLog *log, int32_t target_width, int32_t target_height); +ChiakiErrorCode android_chiaki_video_decoder_init(AndroidChiakiVideoDecoder *decoder, ChiakiLog *log, int32_t target_width, int32_t target_height, ChiakiCodec codec); void android_chiaki_video_decoder_fini(AndroidChiakiVideoDecoder *decoder); void android_chiaki_video_decoder_set_surface(AndroidChiakiVideoDecoder *decoder, JNIEnv *env, jobject surface); bool android_chiaki_video_decoder_video_sample(uint8_t *buf, size_t buf_size, void *user); diff --git a/android/app/src/main/java/com/metallic/chiaki/common/DisplayHost.kt b/android/app/src/main/java/com/metallic/chiaki/common/DisplayHost.kt index b7c22eb..a25f0df 100644 --- a/android/app/src/main/java/com/metallic/chiaki/common/DisplayHost.kt +++ b/android/app/src/main/java/com/metallic/chiaki/common/DisplayHost.kt @@ -10,6 +10,7 @@ sealed class DisplayHost abstract val host: String abstract val name: String? abstract val id: String? + abstract val isPS5: Boolean val isRegistered get() = registeredHost != null } @@ -22,6 +23,7 @@ class DiscoveredDisplayHost( override val host get() = discoveredHost.hostAddr ?: "" override val name get() = discoveredHost.hostName ?: registeredHost?.serverNickname override val id get() = discoveredHost.hostId ?: registeredHost?.serverMac?.toString() + override val isPS5 get() = discoveredHost.isPS5 override fun equals(other: Any?): Boolean = if(other !is DiscoveredDisplayHost) @@ -42,6 +44,7 @@ class ManualDisplayHost( override val host get() = manualHost.host override val name get() = registeredHost?.serverNickname override val id get() = registeredHost?.serverMac?.toString() + override val isPS5: Boolean get() = registeredHost?.target?.isPS5 ?: false override fun equals(other: Any?): Boolean = if(other !is ManualDisplayHost) diff --git a/android/app/src/main/java/com/metallic/chiaki/common/Preferences.kt b/android/app/src/main/java/com/metallic/chiaki/common/Preferences.kt index 3da0a22..c9691b5 100644 --- a/android/app/src/main/java/com/metallic/chiaki/common/Preferences.kt +++ b/android/app/src/main/java/com/metallic/chiaki/common/Preferences.kt @@ -7,6 +7,7 @@ import android.content.SharedPreferences import androidx.annotation.StringRes import androidx.preference.PreferenceManager import com.metallic.chiaki.R +import com.metallic.chiaki.lib.Codec import com.metallic.chiaki.lib.ConnectVideoProfile import com.metallic.chiaki.lib.VideoFPSPreset import com.metallic.chiaki.lib.VideoResolutionPreset @@ -31,12 +32,20 @@ class Preferences(context: Context) FPS_60("60", R.string.preferences_fps_title_60, VideoFPSPreset.FPS_60) } + enum class Codec(val value: String, @StringRes val title: Int, val codec: com.metallic.chiaki.lib.Codec) + { + CODEC_H264("h264", R.string.preferences_codec_title_h264, com.metallic.chiaki.lib.Codec.CODEC_H264), + CODEC_H265("h265", R.string.preferences_codec_title_h265, com.metallic.chiaki.lib.Codec.CODEC_H265) + } + companion object { val resolutionDefault = Resolution.RES_720P val resolutionAll = Resolution.values() val fpsDefault = FPS.FPS_60 val fpsAll = FPS.values() + val codecDefault = Codec.CODEC_H265 + val codecAll = Codec.values() } private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) @@ -97,12 +106,19 @@ class Preferences(context: Context) private val bitrateAutoSubject by lazy { BehaviorSubject.createDefault(bitrateAuto) } val bitrateAutoObservable: Observable get() = bitrateAutoSubject - private val videoProfileDefaultBitrate get() = ConnectVideoProfile.preset(resolution.preset, fps.preset) + val codecKey get() = resources.getString(R.string.preferences_codec_key) + var codec + get() = sharedPreferences.getString(codecKey, codecDefault.value)?.let { value -> + Codec.values().firstOrNull { it.value == value } + } ?: codecDefault + set(value) { sharedPreferences.edit().putString(codecKey, value.value).apply() } + + private val videoProfileDefaultBitrate get() = ConnectVideoProfile.preset(resolution.preset, fps.preset, codec.codec) val videoProfile get() = videoProfileDefaultBitrate.let { val bitrate = bitrate if(bitrate == null) it else - ConnectVideoProfile(it.width, it.height, it.maxFPS, bitrate) + it.copy(bitrate = bitrate) } } \ No newline at end of file diff --git a/android/app/src/main/java/com/metallic/chiaki/common/SerializedSettings.kt b/android/app/src/main/java/com/metallic/chiaki/common/SerializedSettings.kt index 7da4109..8334dc5 100644 --- a/android/app/src/main/java/com/metallic/chiaki/common/SerializedSettings.kt +++ b/android/app/src/main/java/com/metallic/chiaki/common/SerializedSettings.kt @@ -106,7 +106,7 @@ private fun Moshi.serializedSettingsAdapter() = private const val KEY_FORMAT = "format" private const val FORMAT = "chiaki-settings" private const val KEY_VERSION = "version" -private const val VERSION = 1 +private const val VERSION = 2 private const val KEY_SETTINGS = "settings" fun exportAllSettings(db: AppDatabase) = SerializedSettings.fromDatabase(db) diff --git a/android/app/src/main/java/com/metallic/chiaki/discovery/DiscoveryManager.kt b/android/app/src/main/java/com/metallic/chiaki/discovery/DiscoveryManager.kt index 5d23ae5..e7b2e3e 100644 --- a/android/app/src/main/java/com/metallic/chiaki/discovery/DiscoveryManager.kt +++ b/android/app/src/main/java/com/metallic/chiaki/discovery/DiscoveryManager.kt @@ -21,7 +21,7 @@ import java.nio.charset.Charset import java.nio.charset.StandardCharsets import java.util.concurrent.TimeUnit -val DiscoveryHost.ps4Mac get() = this.hostId?.hexToByteArray()?.let { +val DiscoveryHost.serverMac get() = this.hostId?.hexToByteArray()?.let { if(it.size == MacAddress.LENGTH) MacAddress(it) else diff --git a/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt b/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt index ac3f100..d638cb2 100644 --- a/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt +++ b/android/app/src/main/java/com/metallic/chiaki/lib/Chiaki.kt @@ -41,23 +41,32 @@ enum class VideoFPSPreset(val value: Int) FPS_60(60) } +enum class Codec(val value: Int) +{ + CODEC_H264(0), + CODEC_H265(1), + CODEC_H265_HDR(2) +} + @Parcelize data class ConnectVideoProfile( val width: Int, val height: Int, val maxFPS: Int, - val bitrate: Int + val bitrate: Int, + val codec: Codec ): Parcelable { companion object { - fun preset(resolutionPreset: VideoResolutionPreset, fpsPreset: VideoFPSPreset) - = ChiakiNative.videoProfilePreset(resolutionPreset.value, fpsPreset.value) + fun preset(resolutionPreset: VideoResolutionPreset, fpsPreset: VideoFPSPreset, codec: Codec) + = ChiakiNative.videoProfilePreset(resolutionPreset.value, fpsPreset.value, codec) } } @Parcelize data class ConnectInfo( + val ps5: Boolean, val host: String, val registKey: ByteArray, val morning: ByteArray, @@ -76,7 +85,7 @@ private class ChiakiNative @JvmStatic external fun errorCodeToString(value: Int): String @JvmStatic external fun quitReasonToString(value: Int): String @JvmStatic external fun quitReasonIsStopped(value: Int): Boolean - @JvmStatic external fun videoProfilePreset(resolutionPreset: Int, fpsPreset: Int): ConnectVideoProfile + @JvmStatic external fun videoProfilePreset(resolutionPreset: Int, fpsPreset: Int, codec: Codec): ConnectVideoProfile @JvmStatic external fun sessionCreate(result: CreateResult, connectInfo: ConnectInfo, logFile: String?, logVerbose: Boolean, javaSession: Session) @JvmStatic external fun sessionFree(ptr: Long) @JvmStatic external fun sessionStart(ptr: Long): Int @@ -284,6 +293,8 @@ data class DiscoveryHost( READY, STANDBY } + + val isPS5 get() = deviceDiscoveryProtocolVersion == "00030010" } diff --git a/android/app/src/main/java/com/metallic/chiaki/main/DisplayHostRecyclerViewAdapter.kt b/android/app/src/main/java/com/metallic/chiaki/main/DisplayHostRecyclerViewAdapter.kt index 4dd9a69..0bd844e 100644 --- a/android/app/src/main/java/com/metallic/chiaki/main/DisplayHostRecyclerViewAdapter.kt +++ b/android/app/src/main/java/com/metallic/chiaki/main/DisplayHostRecyclerViewAdapter.kt @@ -75,15 +75,18 @@ class DisplayHostRecyclerViewAdapter( } ?: "" it.discoveredIndicatorLayout.visibility = if(host is DiscoveredDisplayHost) View.VISIBLE else View.GONE it.stateIndicatorImageView.setImageResource( - if(host is DiscoveredDisplayHost) - when(host.discoveredHost.state) + when + { + host is DiscoveredDisplayHost -> when(host.discoveredHost.state) { - DiscoveryHost.State.STANDBY -> R.drawable.ic_console_standby - DiscoveryHost.State.READY -> R.drawable.ic_console_ready - else -> R.drawable.ic_console + DiscoveryHost.State.STANDBY -> if(host.isPS5) R.drawable.ic_console_ps5_standby else R.drawable.ic_console_standby + DiscoveryHost.State.READY -> if(host.isPS5) R.drawable.ic_console_ps5_ready else R.drawable.ic_console_ready + else -> if(host.isPS5) R.drawable.ic_console_ps5 else R.drawable.ic_console } - else - R.drawable.ic_console) + host.isPS5 -> R.drawable.ic_console_ps5 + else -> R.drawable.ic_console + } + ) it.setOnClickListener { clickCallback(host) } val canWakeup = host.registeredHost != null diff --git a/android/app/src/main/java/com/metallic/chiaki/main/MainActivity.kt b/android/app/src/main/java/com/metallic/chiaki/main/MainActivity.kt index 975fd2f..4fd8178 100644 --- a/android/app/src/main/java/com/metallic/chiaki/main/MainActivity.kt +++ b/android/app/src/main/java/com/metallic/chiaki/main/MainActivity.kt @@ -170,7 +170,7 @@ class MainActivity : AppCompatActivity() if(registeredHost != null) { fun connect() { - val connectInfo = ConnectInfo(host.host, registeredHost.rpRegistKey, registeredHost.rpKey, Preferences(this).videoProfile) + val connectInfo = ConnectInfo(host.isPS5, host.host, registeredHost.rpRegistKey, registeredHost.rpKey, Preferences(this).videoProfile) Intent(this, StreamActivity::class.java).let { it.putExtra(StreamActivity.EXTRA_CONNECT_INFO, connectInfo) startActivity(it) diff --git a/android/app/src/main/java/com/metallic/chiaki/main/MainViewModel.kt b/android/app/src/main/java/com/metallic/chiaki/main/MainViewModel.kt index 0ed203a..b4fe9c8 100644 --- a/android/app/src/main/java/com/metallic/chiaki/main/MainViewModel.kt +++ b/android/app/src/main/java/com/metallic/chiaki/main/MainViewModel.kt @@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModel import com.metallic.chiaki.common.* import com.metallic.chiaki.common.ext.toLiveData import com.metallic.chiaki.discovery.DiscoveryManager -import com.metallic.chiaki.discovery.ps4Mac +import com.metallic.chiaki.discovery.serverMac import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.Observables @@ -34,7 +34,7 @@ class MainViewModel(val database: AppDatabase, val preferences: Preferences): Vi val macRegisteredHosts = registeredHosts.associateBy { it.serverMac } val idRegisteredHosts = registeredHosts.associateBy { it.id } discoveredHosts.map { - DiscoveredDisplayHost(it.ps4Mac?.let { mac -> macRegisteredHosts[mac] }, it) + DiscoveredDisplayHost(it.serverMac?.let { mac -> macRegisteredHosts[mac] }, it) } + manualHosts.map { ManualDisplayHost(it.registeredHost?.let { id -> idRegisteredHosts[id] }, it) diff --git a/android/app/src/main/java/com/metallic/chiaki/regist/RegistActivity.kt b/android/app/src/main/java/com/metallic/chiaki/regist/RegistActivity.kt index 4f5af54..1332fdc 100644 --- a/android/app/src/main/java/com/metallic/chiaki/regist/RegistActivity.kt +++ b/android/app/src/main/java/com/metallic/chiaki/regist/RegistActivity.kt @@ -49,27 +49,29 @@ class RegistActivity: AppCompatActivity(), RevealActivity registButton.setOnClickListener { doRegist() } - ps4VersionRadioGroup.check(when(viewModel.ps4Version.value ?: RegistViewModel.PS4Version.GE_8) { - RegistViewModel.PS4Version.GE_8 -> R.id.ps4VersionGE8RadioButton - RegistViewModel.PS4Version.GE_7 -> R.id.ps4VersionGE7RadioButton - RegistViewModel.PS4Version.LT_7 -> R.id.ps4VersionLT7RadioButton + ps4VersionRadioGroup.check(when(viewModel.ps4Version.value ?: RegistViewModel.ConsoleVersion.PS5) { + RegistViewModel.ConsoleVersion.PS5 -> R.id.ps5RadioButton + RegistViewModel.ConsoleVersion.PS4_GE_8 -> R.id.ps4VersionGE8RadioButton + RegistViewModel.ConsoleVersion.PS4_GE_7 -> R.id.ps4VersionGE7RadioButton + RegistViewModel.ConsoleVersion.PS4_LT_7 -> R.id.ps4VersionLT7RadioButton }) ps4VersionRadioGroup.setOnCheckedChangeListener { _, checkedId -> viewModel.ps4Version.value = when(checkedId) { - R.id.ps4VersionGE8RadioButton -> RegistViewModel.PS4Version.GE_8 - R.id.ps4VersionGE7RadioButton -> RegistViewModel.PS4Version.GE_7 - R.id.ps4VersionLT7RadioButton -> RegistViewModel.PS4Version.LT_7 - else -> RegistViewModel.PS4Version.GE_7 + R.id.ps5RadioButton -> RegistViewModel.ConsoleVersion.PS5 + R.id.ps4VersionGE8RadioButton -> RegistViewModel.ConsoleVersion.PS4_GE_8 + R.id.ps4VersionGE7RadioButton -> RegistViewModel.ConsoleVersion.PS4_GE_7 + R.id.ps4VersionLT7RadioButton -> RegistViewModel.ConsoleVersion.PS4_LT_7 + else -> RegistViewModel.ConsoleVersion.PS5 } } viewModel.ps4Version.observe(this, Observer { - psnAccountIdHelpGroup.visibility = if(it == RegistViewModel.PS4Version.LT_7) View.GONE else View.VISIBLE + psnAccountIdHelpGroup.visibility = if(it == RegistViewModel.ConsoleVersion.PS4_LT_7) View.GONE else View.VISIBLE psnIdTextInputLayout.hint = getString(when(it!!) { - RegistViewModel.PS4Version.LT_7 -> R.string.hint_regist_psn_online_id + RegistViewModel.ConsoleVersion.PS4_LT_7 -> R.string.hint_regist_psn_online_id else -> R.string.hint_regist_psn_account_id }) }) @@ -77,22 +79,22 @@ class RegistActivity: AppCompatActivity(), RevealActivity private fun doRegist() { - val ps4Version = viewModel.ps4Version.value ?: RegistViewModel.PS4Version.GE_7 + val ps4Version = viewModel.ps4Version.value ?: RegistViewModel.ConsoleVersion.PS5 val host = hostEditText.text.toString().trim() val hostValid = host.isNotEmpty() val broadcast = broadcastCheckBox.isChecked val psnId = psnIdEditText.text.toString().trim() - val psnOnlineId: String? = if(ps4Version == RegistViewModel.PS4Version.LT_7) psnId else null + val psnOnlineId: String? = if(ps4Version == RegistViewModel.ConsoleVersion.PS4_LT_7) psnId else null val psnAccountId: ByteArray? = - if(ps4Version != RegistViewModel.PS4Version.LT_7) + if(ps4Version != RegistViewModel.ConsoleVersion.PS4_LT_7) try { Base64.decode(psnId, Base64.DEFAULT) } catch(e: IllegalArgumentException) { null } else null val psnIdValid = when(ps4Version) { - RegistViewModel.PS4Version.LT_7 -> psnOnlineId?.isNotEmpty() ?: false + RegistViewModel.ConsoleVersion.PS4_LT_7 -> psnOnlineId?.isNotEmpty() ?: false else -> psnAccountId != null && psnAccountId.size == RegistInfo.ACCOUNT_ID_SIZE } @@ -105,7 +107,7 @@ class RegistActivity: AppCompatActivity(), RevealActivity if(!psnIdValid) getString(when(ps4Version) { - RegistViewModel.PS4Version.LT_7 -> R.string.regist_psn_online_id_invalid + RegistViewModel.ConsoleVersion.PS4_LT_7 -> R.string.regist_psn_online_id_invalid else -> R.string.regist_psn_account_id_invalid }) else @@ -117,9 +119,10 @@ class RegistActivity: AppCompatActivity(), RevealActivity val target = when(ps4Version) { - RegistViewModel.PS4Version.GE_8 -> Target.PS4_10 - RegistViewModel.PS4Version.GE_7 -> Target.PS4_9 - RegistViewModel.PS4Version.LT_7 -> Target.PS4_8 + RegistViewModel.ConsoleVersion.PS5 -> Target.PS5_1 + RegistViewModel.ConsoleVersion.PS4_GE_8 -> Target.PS4_10 + RegistViewModel.ConsoleVersion.PS4_GE_7 -> Target.PS4_9 + RegistViewModel.ConsoleVersion.PS4_LT_7 -> Target.PS4_8 } val registInfo = RegistInfo(target, host, broadcast, psnOnlineId, psnAccountId, pin.toInt()) diff --git a/android/app/src/main/java/com/metallic/chiaki/regist/RegistViewModel.kt b/android/app/src/main/java/com/metallic/chiaki/regist/RegistViewModel.kt index d99778e..31c6bda 100644 --- a/android/app/src/main/java/com/metallic/chiaki/regist/RegistViewModel.kt +++ b/android/app/src/main/java/com/metallic/chiaki/regist/RegistViewModel.kt @@ -7,11 +7,12 @@ import androidx.lifecycle.ViewModel class RegistViewModel: ViewModel() { - enum class PS4Version { - GE_8, - GE_7, - LT_7 + enum class ConsoleVersion { + PS5, + PS4_GE_8, + PS4_GE_7, + PS4_LT_7 } - val ps4Version = MutableLiveData(PS4Version.GE_8) + val ps4Version = MutableLiveData(ConsoleVersion.PS5) } \ No newline at end of file diff --git a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsFragment.kt b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsFragment.kt index 2001eed..f38f619 100644 --- a/android/app/src/main/java/com/metallic/chiaki/settings/SettingsFragment.kt +++ b/android/app/src/main/java/com/metallic/chiaki/settings/SettingsFragment.kt @@ -16,6 +16,7 @@ import com.metallic.chiaki.common.exportAndShareAllSettings import com.metallic.chiaki.common.ext.viewModelFactory import com.metallic.chiaki.common.getDatabase import com.metallic.chiaki.common.importSettingsFromUri +import com.metallic.chiaki.lib.Codec import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.addTo @@ -42,6 +43,7 @@ class DataStore(val preferences: Preferences): PreferenceDataStore() preferences.resolutionKey -> preferences.resolution.value preferences.fpsKey -> preferences.fps.value preferences.bitrateKey -> preferences.bitrate?.toString() ?: "" + preferences.codecKey -> preferences.codec.value else -> defValue } @@ -60,6 +62,11 @@ class DataStore(val preferences: Preferences): PreferenceDataStore() preferences.fps = fps } preferences.bitrateKey -> preferences.bitrate = value?.toIntOrNull() + preferences.codecKey -> + { + val codec = Preferences.Codec.values().firstOrNull { it.value == value } ?: return + preferences.codec = codec + } } } } @@ -111,6 +118,11 @@ class SettingsFragment: PreferenceFragmentCompat(), TitleFragment bitratePreference?.summaryProvider = bitrateSummaryProvider }) + preferenceScreen.findPreference(getString(R.string.preferences_codec_key))?.let { + it.entryValues = Preferences.codecAll.map { codec -> codec.value }.toTypedArray() + it.entries = Preferences.codecAll.map { codec -> getString(codec.title) }.toTypedArray() + } + val registeredHostsPreference = preferenceScreen.findPreference("registered_hosts") viewModel.registeredHostsCount.observe(this, Observer { registeredHostsPreference?.summary = getString(R.string.preferences_registered_hosts_summary, it) diff --git a/android/app/src/main/res/drawable/ic_codec.xml b/android/app/src/main/res/drawable/ic_codec.xml new file mode 100644 index 0000000..e173d90 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_codec.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/android/app/src/main/res/drawable/ic_console_ps5.xml b/android/app/src/main/res/drawable/ic_console_ps5.xml new file mode 100644 index 0000000..488213b --- /dev/null +++ b/android/app/src/main/res/drawable/ic_console_ps5.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/app/src/main/res/drawable/ic_console_ps5_ready.xml b/android/app/src/main/res/drawable/ic_console_ps5_ready.xml new file mode 100644 index 0000000..d2a3ab0 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_console_ps5_ready.xml @@ -0,0 +1,16 @@ + + + + diff --git a/android/app/src/main/res/drawable/ic_console_ps5_standby.xml b/android/app/src/main/res/drawable/ic_console_ps5_standby.xml new file mode 100644 index 0000000..8d49bbd --- /dev/null +++ b/android/app/src/main/res/drawable/ic_console_ps5_standby.xml @@ -0,0 +1,16 @@ + + + + diff --git a/android/app/src/main/res/layout/activity_regist.xml b/android/app/src/main/res/layout/activity_regist.xml index e174bb0..607fcc8 100644 --- a/android/app/src/main/res/layout/activity_regist.xml +++ b/android/app/src/main/res/layout/activity_regist.xml @@ -66,9 +66,17 @@ android:id="@+id/ps4VersionRadioGroup" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="horizontal" + android:orientation="vertical" app:layout_constraintTop_toBottomOf="@id/broadcastCheckBox"> + + + android:layout_weight="1"/> + android:layout_marginTop="4dp" + android:layout_marginBottom="4dp"/> PS4 < 7.0 PS4 ≥ 7.0, < 8 PS4 ≥ 8.0 + PS5 About obtaining your Account ID, see https://git.sr.ht/~thestr4ng3r/chiaki/tree/master/item/README.md#obtaining-your-psn-accountid PSN Online ID (username, case-sensitive) @@ -71,6 +72,7 @@ Resolution FPS Bitrate + Codec Verbose Logging Warning: This logs a LOT! Don\'t enable for regular use. 360p @@ -80,6 +82,8 @@ 30 60 Auto (%d) + H264 + H265 (PS5 only) Swap Cross/Moon and Box/Pyramid Buttons Swap face buttons if default mapping is incorrect (e.g. for 8BitDo controllers) Are you sure you want to delete the registered console %s with ID %s? @@ -103,4 +107,5 @@ stream_resolution stream_fps stream_bitrate + stream_codec diff --git a/android/app/src/main/res/xml/preferences.xml b/android/app/src/main/res/xml/preferences.xml index d656347..0416f6e 100644 --- a/android/app/src/main/res/xml/preferences.xml +++ b/android/app/src/main/res/xml/preferences.xml @@ -52,6 +52,12 @@ app:key="@string/preferences_bitrate_key" app:title="@string/preferences_bitrate_title" app:icon="@drawable/ic_bitrate"/> + +