diff --git a/app/src/main/java/com/huawei/cloudapp/ui/activity/CasCloudPhoneActivity.java b/app/src/main/java/com/huawei/cloudapp/ui/activity/CasCloudPhoneActivity.java index 91efb6654702ca8733f43ba558a3e6ae53299d5c..f4445eddb9e7a2652e22956a78aa53d65ae4e3d0 100644 --- a/app/src/main/java/com/huawei/cloudapp/ui/activity/CasCloudPhoneActivity.java +++ b/app/src/main/java/com/huawei/cloudapp/ui/activity/CasCloudPhoneActivity.java @@ -62,6 +62,7 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.graphics.Color; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -74,6 +75,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.Window; import android.view.WindowManager; +import android.widget.CheckBox; import android.widget.FrameLayout; import android.widget.ProgressBar; import android.widget.TextView; @@ -116,6 +118,8 @@ import com.huawei.cloudphone.api.ICloudPhone; import java.util.HashMap; import java.util.List; +import java.util.Timer; +import java.util.TimerTask; public class CasCloudPhoneActivity extends FragmentActivity implements IHandleData { private static final String TAG = "CasCloudPhoneActivity"; @@ -164,6 +168,9 @@ public class CasCloudPhoneActivity extends FragmentActivity implements IHandleDa private boolean mIsStopCloudPhoneCalled = false; private ICloudPhone mCloudPhone; private TextView mNavigationTextView; + private TextView mStatisticsTextView; + private CheckBox mStatisticsCheckBox; + private Timer mTimer = new Timer(); private boolean isReadyToExit; private Toast mToast; @@ -243,6 +250,27 @@ public class CasCloudPhoneActivity extends FragmentActivity implements IHandleDa mProgressBar = findViewById(R.id.loading_progress_bar); mFrameLayout = (FrameLayout) findViewById(R.id.frame_layout); + mStatisticsTextView = findViewById(R.id.cas_statistic_view); + mStatisticsTextView.setTextColor(Color.GREEN); + + mStatisticsCheckBox = findViewById(R.id.cas_stats_checkBox); + mStatisticsCheckBox.setTextColor(Color.GREEN); + mStatisticsCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (isChecked) { + mStatisticsTextView.setVisibility(View.VISIBLE); + float density = getResources().getDisplayMetrics().density; + mStatisticsTextView.setHeight((int) (50 * density)); + } else { + mStatisticsTextView.setVisibility(View.INVISIBLE); + } + }); + mTimer.schedule(new TimerTask() { + @Override + public void run() { + runOnUiThread(() -> onUpdateStatisticInfo()); + } + }, 1000, 1000); // 1000 毫秒后执行,之后每隔 1000 毫秒执行一次 + mNavigationTextView = findViewById(R.id.cas_navigaiton); mNavigationTextView.setOnTouchListener(new View.OnTouchListener() { @SuppressLint({"RestrictedApi", "ClickableViewAccessibility"}) @@ -309,6 +337,13 @@ public class CasCloudPhoneActivity extends FragmentActivity implements IHandleDa } } + public void onUpdateStatisticInfo() { + if (mStatisticsTextView.getVisibility() != View.VISIBLE) { + return; + } + mStatisticsTextView.setText(mCloudPhone.getVideoStatisticInfo()); + } + /** * Activity:onStart */ diff --git a/app/src/main/res/drawable/rounded_corner.xml b/app/src/main/res/drawable/rounded_corner.xml new file mode 100644 index 0000000000000000000000000000000000000000..57eef4f29c874a82d2e38d6d9cd59c3bb334271b --- /dev/null +++ b/app/src/main/res/drawable/rounded_corner.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/cas_activity_fullscreen.xml b/app/src/main/res/layout/cas_activity_fullscreen.xml index 50d47614d940212ac1ff458378fdaad3cd6ff891..7caa7c27b64efb9f9b635b98ed5b655334e37fdf 100644 --- a/app/src/main/res/layout/cas_activity_fullscreen.xml +++ b/app/src/main/res/layout/cas_activity_fullscreen.xml @@ -19,6 +19,24 @@ android:layout_width="match_parent" android:layout_height="match_parent"/> + + + + #include #include +#include #include "CasJniBridge.h" #include "CasController.h" #include "CasConf.h" @@ -23,6 +24,7 @@ #include "CasMsgCode.h" #include "CasExtVideoDataPipe.h" #include "opus.h" +#include "CasVideoUtil.h" using namespace std; @@ -36,6 +38,7 @@ const int TIMES = 8; const int FRAME_RATE_MIN = 10; const int FRAME_RATE_MAX = 60; const std::string CLIENT_TYPE = "1"; +const uint64_t DURATION_USEC = 1000000ULL; int32_t OnRecvVideoStreamData(uint8_t* data, uint32_t length); int32_t OnRecvAudioStreamData(uint8_t* data, uint32_t length); @@ -1221,6 +1224,7 @@ void CasController::RecvdVideoData(uint8_t *data, int length) uint8_t *videoData = new uint8_t[length]; memcpy(videoData, data, length); m_videoPacketStream->Handle(videoData); + CalculateFPS(); } m_isMTransValid = true; } @@ -1333,4 +1337,53 @@ void CasController::HandleCmdData(uint8_t *data, int length) serviceHandle->Handle((void *)pTmp); } } +} + +std::string CasController::GetVideoRecvStats() { + std::string statsString; + std::stringstream stream; +#if MTRANS_ENABLED + if (m_mtrans != nullptr && m_isMTransValid) { + + int rtt = m_mtrans->GetRtt(); + stream << "网络时延 : "; + if (rtt != -1) { + stream << rtt << "ms"; + } + stream << std::endl; + + StreamRecvStats stats{}; + if (0 != m_mtrans->GetVideoRecvStats(&stats)) { + WARN("GetVideoRecvStats failed"); + } else { + stream << "下行视频丢包 : " << stats.lostRate << std::endl; + stream << "视频接收码率 : " << stats.recvBitrate << "kbps" << std::endl; + } + StreamRecvStats audioStats{}; + if (0 != m_mtrans->GetAudioRecvStats(&audioStats)) { + WARN("GetAudioRecvStats failed"); + } else { + stream << "音频接收码率 : " << audioStats.recvBitrate << "kbps" << std::endl; + } + } +#endif + stream << "帧率 : " << m_currentFPS << "fps" << std::endl; + + statsString = stream.str(); + return statsString; +} + +void CasController::CalculateFPS() { + uint64_t currentTime = CasVideoUtil::GetInstance()->GetNow(); + + m_videoDataCount++; + + if (m_lastTimeGotVideoData == 0) { + m_lastTimeGotVideoData = currentTime; + } + if (currentTime - m_lastTimeGotVideoData > DURATION_USEC) { + m_currentFPS = m_videoDataCount; + m_videoDataCount = 0; + m_lastTimeGotVideoData = currentTime; + } } \ No newline at end of file diff --git a/cloudphone/src/main/cpp/CasController.h b/cloudphone/src/main/cpp/CasController.h index ab7e9ac17a40c7e17f3919e15273df360b5bbc51..3aa6a97e2a5fb7bccb0e78d7b3135b6ed57a62c0 100644 --- a/cloudphone/src/main/cpp/CasController.h +++ b/cloudphone/src/main/cpp/CasController.h @@ -87,6 +87,8 @@ public: void HandleCmdData(uint8_t *data, int length); + std::string GetVideoRecvStats(); + private: bool Release(); @@ -130,6 +132,8 @@ private: std::string CalcMaxDisconnectDuration(std::string backgroundTimeout); + void CalculateFPS(); + void *(*cmdCallBack)(int type, std::string msg) = nullptr; static CasController *g_instance; @@ -179,6 +183,9 @@ private: int m_orientation = 0; int m_rotationDegrees = 0; bool m_isMTransValid = false; + int m_videoDataCount = 0; + int m_currentFPS = 0; + uint64_t m_lastTimeGotVideoData = 0; }; #endif // CLOUDAPPSDK_CASCONTROLLRT_H \ No newline at end of file diff --git a/cloudphone/src/main/cpp/CasJniBridge.cpp b/cloudphone/src/main/cpp/CasJniBridge.cpp index 939c953e78119bf7ee5ad4d0566acaa51b9bcfb8..3f803080d91b4d182e624b1fb8091ea98ca3c843 100644 --- a/cloudphone/src/main/cpp/CasJniBridge.cpp +++ b/cloudphone/src/main/cpp/CasJniBridge.cpp @@ -214,6 +214,12 @@ extern "C" JNIEXPORT jint JNICALL JNI(getLag)(JNIEnv *env, jclass) return lag; } +extern "C" JNIEXPORT jstring JNICALL JNI(getVideoStreamStats)(JNIEnv *env, jclass) +{ + string statsString = gJniApiCtrl->GetVideoRecvStats(); + return env->NewStringUTF(statsString.c_str()); +} + extern "C" JNIEXPORT void JNICALL JNI(setAssetsData)(JNIEnv *env, jclass, jstring jFilename, jbyteArray jData, jint length) { diff --git a/cloudphone/src/main/cpp/CasJniBridge.h b/cloudphone/src/main/cpp/CasJniBridge.h index 47a4c8d78b5e081bef95cf070e2e09c6e05125f7..88ea69431569a17b513042e9585c65fbdf658c68 100644 --- a/cloudphone/src/main/cpp/CasJniBridge.h +++ b/cloudphone/src/main/cpp/CasJniBridge.h @@ -34,6 +34,8 @@ JNIEXPORT int JNICALL JNI(getJniStatus)(JNIEnv *env, jclass); JNIEXPORT int JNICALL JNI(getLag)(JNIEnv *env, jclass); +JNIEXPORT jstring JNICALL JNI(getVideoStreamStats)(JNIEnv *env, jclass); + JNIEXPORT jboolean JNICALL JNI(setMediaConfig)(JNIEnv *env, jclass, jobject mediaConfig); JNIEXPORT jint JNICALL JNI(recvData)(JNIEnv *env, jclass, jbyte type, jbyteArray data, int length); diff --git a/cloudphone/src/main/cpp/libs/Arm32/libmtrans.a b/cloudphone/src/main/cpp/libs/Arm32/libmtrans.a index 5140bfd878c72f956a55cbf4d5fd38a447277698..c5ea687fc5b3b4d8e3f0d0ae71b159764423fe2a 100644 Binary files a/cloudphone/src/main/cpp/libs/Arm32/libmtrans.a and b/cloudphone/src/main/cpp/libs/Arm32/libmtrans.a differ diff --git a/cloudphone/src/main/cpp/libs/Arm64/libmtrans.a b/cloudphone/src/main/cpp/libs/Arm64/libmtrans.a index 3f614113cdf77060ebc7a300b016ad1497854ff1..af361d9ba15ba74d1dd334dee88284ea8cca3b87 100644 Binary files a/cloudphone/src/main/cpp/libs/Arm64/libmtrans.a and b/cloudphone/src/main/cpp/libs/Arm64/libmtrans.a differ diff --git a/cloudphone/src/main/cpp/libs/mtrans/include/net_trans.h b/cloudphone/src/main/cpp/libs/mtrans/include/net_trans.h index 5a55400f2818a76cbc513b1e225cad23034c1b86..6b11c44a087f2c447a03e5fd94dee624a10087e6 100644 --- a/cloudphone/src/main/cpp/libs/mtrans/include/net_trans.h +++ b/cloudphone/src/main/cpp/libs/mtrans/include/net_trans.h @@ -51,6 +51,9 @@ public: int SendKeyEventData(uint8_t *data, int len); int SendMotionEventData(uint8_t *data, int len); + int GetVideoRecvStats(StreamRecvStats* stats); + int GetAudioRecvStats(StreamRecvStats* stats); + int GetRtt(); private: class NetTransPri; diff --git a/cloudphone/src/main/cpp/libs/mtrans/include/net_trans_def.h b/cloudphone/src/main/cpp/libs/mtrans/include/net_trans_def.h index 46579e1758b7361c0d426fcbea8a602855dd4e58..f5c4331975f6fe04a45d8c525cd51a28fa6dd51e 100644 --- a/cloudphone/src/main/cpp/libs/mtrans/include/net_trans_def.h +++ b/cloudphone/src/main/cpp/libs/mtrans/include/net_trans_def.h @@ -70,6 +70,51 @@ struct MotionEventParam { int32_t secondaryValue; } __attribute__((packed)); +struct VideoRecvStats { + uint32_t recvFrameRate; + uint32_t redRate; + uint32_t recoverRate; + uint32_t jbListpacketNum; + uint32_t sendKeyRequestCnt; + int32_t jbJitter; + uint32_t refFrameErrorCnt; + uint64_t aveJbDelayAfterBuild; + uint64_t recvAveDelay; + uint64_t recvMaxDelay; +}; + +struct AudioRecvStats { + uint32_t receiveTotalTime; + uint32_t plcCount; + uint32_t tsmCompressCount; + uint32_t tsmStretchCount; + int32_t totalDelay; + int32_t estTotalDelay; + uint64_t noVoiceFrameCnt; + uint32_t getFrameCount; +}; + +struct StreamRecvStats { + uint32_t recvBitrate; + uint32_t lostRate; + uint32_t jitter; + uint32_t lostPktCnt; + uint32_t maxContLostPktCnt; + uint32_t recvTotalFrameCnt; + uint32_t jbDepth; + uint32_t jbTotalFrameCnt; + uint32_t packetRate; + uint32_t periodNotRecvPktCnt; + uint32_t periodNotRecvPktTime; + uint32_t periodFreezeFrameCnt; + uint32_t periodFreezeTime; + uint32_t sendNackRequestCnt; + union { + VideoRecvStats videoStats; + AudioRecvStats audioStats; + }; +}; + typedef int32_t (*RecvVideoDataCallback)(uint8_t* data, uint32_t length); typedef int32_t (*RecvAudioDataCallback)(uint8_t* data, uint32_t length); typedef int32_t (*RecvAudioDecodeCallback)(int32_t streamId, AudioJbDecode* audioJbDecode); diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/api/ICloudPhone.java b/cloudphone/src/main/java/com/huawei/cloudphone/api/ICloudPhone.java index a08203106075477d969deb2890828c44d792736a..dfb1c5db1f699f474b0654f6253de29fb238052a 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/api/ICloudPhone.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/api/ICloudPhone.java @@ -152,4 +152,6 @@ public interface ICloudPhone { * @return 当前状态 */ int getState(); + + String getVideoStatisticInfo(); } diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/apiimpl/CloudPhoneImpl.java b/cloudphone/src/main/java/com/huawei/cloudphone/apiimpl/CloudPhoneImpl.java index a17ba5f57d88fdbab196ef15c78e1e65d791fbad..734f7e06efc6ecddef4b39a907386ead85c944b4 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/apiimpl/CloudPhoneImpl.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/apiimpl/CloudPhoneImpl.java @@ -346,6 +346,15 @@ public class CloudPhoneImpl implements ICloudPhone { } } + @Override + public String getVideoStatisticInfo() { + if (mProccessor != null) { + return mProccessor.getVideoStreamStats(); + } else { + return null; + } + } + @Override public int getState() { return mCurrentState; diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JNIWrapper.java b/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JNIWrapper.java index cb49f18bd67b2bc445bbd35db391341ad89b65be..4f789a948c2936005cdcb7cfda7e17b7b8150ee4 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JNIWrapper.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JNIWrapper.java @@ -90,6 +90,8 @@ public class JNIWrapper { public static native int getLag(); + public static native String getVideoStreamStats(); + public static native boolean setMediaConfig(HashMap mediaConfigMap); public static native boolean setRotation(int rotation); diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JniBridge.java b/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JniBridge.java index 566e030f3f8f1bfb0f3ab03231f147e34be8fb1b..0a41f1951f2e5402d7030186282c023b8de70b51 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JniBridge.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JniBridge.java @@ -85,6 +85,10 @@ public class JniBridge { return JNIWrapper.getLag(); } + public String getVideoStreamStats() { + return JNIWrapper.getVideoStreamStats(); + } + public boolean setMediaConfig(HashMap mediaConfigMap) { if (mediaConfigMap != null) { return JNIWrapper.setMediaConfig(mediaConfigMap); diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/service/CasProcessor.java b/cloudphone/src/main/java/com/huawei/cloudphone/service/CasProcessor.java index 18608524e86989e37c96352046b71439c3cbc573..d049c63d3208b4cf60af171db0871e8b22c1870c 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/service/CasProcessor.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/service/CasProcessor.java @@ -190,6 +190,9 @@ public class CasProcessor { return JniBridge.getInstance().getLag(); } + public String getVideoStreamStats() { + return JniBridge.getInstance().getVideoStreamStats(); + } public boolean startJniRecv() { CASLog.i(TAG, "startJniRecv... "); mUpstreamReceiveDispatcher = new CasRecvPktDispatcher();