Android視訊直播、點播播放器哪家強?

沉默的範大叔發表於2017-11-28

最近在專案中要加入視訊直播和點播功能,那麼問題來了,我需要一個播放器來播放視訊流,那該如何選擇呢?除了原生的VideoView(VideoView表示臣妾做不到啊),還有一些播放器如Vitamio,B站開源的IjkPlayer等,當然各大直播雲服務商也提供了自己的播放器。花兩天時間調研了幾家,順便記錄下來分享給大家,看一看到底哪家強。

太長不看版

這裡選擇了開源播放器IjkPlayer和直播雲廠商播放器PLDroidPlayer作為測試樣本。

資料統計

軟硬編碼
IjkPlayer
PLDroidPlayer
首開(ms) 記憶體 min,avg,max(MB) CPU min,avg,max(%) 首開(ms) 記憶體 min,avg,max(MB) CPU min,avg,max(%)
軟編碼 1559 64.49,110.19,114.92 5.00,30.69,80.72 198 32.34,87.41,93.47 3.11,30.25,67.18
硬編碼 2280 45.37,48.81,52.34 1.36,10.10,17.37 174 30.98,81.67,85.87 2.00,28.00,69.23

包體

對比點
IjkPlayer
PLDroidPlayer
版本 0.8.4 2.0.3
jar/aar包 66KB(java) + 1342KB(armv7a)=1408KB 80KB
so庫(armeabi-v7a,不帶Https功能) 2.58MB 2.27MB
總計 3.96MB 2.35MB

注:

  1. IjkPlayer通過gradle下載下來為aar包,存放在目錄C:\Users\(使用者名稱)\.gradle\caches\modules-2\files-2.1\tv.danmaku.ijk.media。PLDroidPlayer為jar包。
  2. IjkPlayer至少需要用到兩個包,分別是java包和armv7a包。
  3. IjkPlayer和PLDroid均可以支援Https,IjkPlayer需要單獨編譯,PLDroid只需新增libqcOpenSSL.so庫即可。這裡對比的是不帶Https功能的。

功能點

這裡只是對主要功能點進行對比,更多PLDroidPlayer功能點介紹可以檢視github.com/pili-engine…,而ijkPlayer並沒有對其功能進行介紹:github.com/Bilibili/ij…

功能
IjkPlayer
PLDroidPlayer
版本 0.8.4 2.0.3
RTMP 支援 支援
HLS 支援 支援
HTTP-FLV 支援 支援
HTTPS 支援(需要單獨編譯) 支援
硬解碼 支援 支援
是否需要編譯 需要 不需要
播放控制元件 不提供 提供
UI定製 可以 可以
文件 不完善 完善
是否開源 開源 不開源
整合難度 略麻煩 容易
技術支援

測試樣本選擇

先說下在測試樣本選擇上我是如何考慮的:

  1. 開源播放器大V選一家,直播雲廠商選一家。
    ijk無可厚非是開源播放器中的首選,關於如何玩轉ijk的文章也很多,口碑也比較好。而云服務廠商好歹是靠賣直播、點播產品賺錢的,播放器作為直播點播服務中重要的一環也是人家吃飯的工具,而且可以說被很多直播大客戶驗證過的,最重要的這些播放器SDK竟然都是免費的,不拿來用太浪費了。
  2. 視訊雲廠商播放器評測的選擇。
    因為我司原生app本身體積比較大了,所以對我來說包體大小是第一要考慮的點。而所有在雲廠商裡,我選了包體最小的七牛作為測試樣本。大家可以根據自己業務需求再選擇測試樣本。

當然,這裡只是做了幾次資料取樣,需要結果更具說服力,可能還需要更多的測試條件和測試資料,不過我們可以從當前獲取到的資料推斷:

  1. 不管是軟解碼和硬解碼,PLDroidPlayer的首開速度都要遠快於IjkPlayer;
  2. 在軟解碼條件下,PLDroidPlayer的Cpu和記憶體消耗都要略低於IjkPlayer;
  3. 在硬解碼條件下,PLDroidPlayer的Cpu和記憶體消耗都要高於IjkPlayer。

基礎

在進行對比之前,我們需要對直播相關的基礎概念做一些簡單介紹,如果對這一塊比較熟悉的同學可以跳過。

視訊直播

視訊直播就是視訊資料從採集端(攝像頭)通過網路實時推送到播放端(手機,電腦,電視等),我們最早接觸到的視訊直播可能就是電視直播了,但隨著智慧手機發展,移動直播興起,它的視訊採集端是手機,播放端通常也是手機。

視訊點播

視訊點播就是一段已經錄製好的視訊資料,使用者可以點選播放。由於是已經錄製完成的視訊資料,所以還可以控制播放進度。

直播協議

直播協議常見的有三種:RTMP、Http-FLV和HLS。

  • RTMP: 基於TCP協議,由Adobe設計,將音視訊資料切割成小的資料包在網際網路上傳輸,延時3s以內,但拆包組包複雜,在海量併發情況下不穩定。由於不是基於Http協議,存在被防火牆牆掉的可能性。
  • Http-FLV:基於Http協議,由Adobe設計,在大塊音視訊資料頭部新增標記資訊,延時3s以內,海量併發穩定,手機瀏覽器支援不足。
  • HLS:基於Http協議,由Apple設計,將視訊資料切分成片段(10s以內),由m3u8索引檔案進行管理,高延時(10s到30s),手機瀏覽器支援較好,可通過網頁轉發直播連結。

軟編碼和硬編碼

音視訊資料在網際網路上傳輸之前,由於存在冗餘資料,需要進行壓縮編碼,編碼存在兩種方式,一種是軟編碼一種是硬編碼。

  • 軟編碼:使用CPU進行編碼
  • 硬編碼:使用非CPU進行編碼,如GPU等

軟解碼和硬解碼

資料進行編碼之後傳輸到播放端,就要進行解碼,那麼解碼也有兩種方式,一種是軟解碼一種是硬解碼。

  • 軟解碼:使用CPU進行解碼
  • 硬解碼:使用非CPU進行解碼

IjkPlayer

IjkPlayer是B站開源播放器,地址為:github.com/Bilibili/ij…,基於音視訊編解碼庫FFmpeg,支援常用的直播協議。IjkPlayer只提供播放器引擎庫,不提供UI介面,所以使用IjkPlayer時還需要對UI介面進行二次封裝,不過Github上有一些基於ijkplayer二次開發的播放器,他們對UI介面做了比較好的封裝。

通過git命令:

git clone https://github.com/Bilibili/ijkplayer.git複製程式碼

下載ijkplayer完整專案,然後使用Android Studio開啟目錄:ijkplayer\android\ijkplayer,這個是Android的Demo專案,執行之後,效果如下:

但是,我們暫時還是無法播放示例視訊列表當中的視訊,還需要編譯so庫。在編譯so庫的過程中,躺坑躺到懷疑人生,跟大家分享一下,避免跟我一樣踩坑。

Windows下編譯IjkPlayer

首先大家最好不要在Windows環境下編譯,因為我使用的是Windows系統,所以沒有多想,下載程式碼後安裝Cygwin,然後在Cygwin中安裝make,yasm,裝完之後以為大功告成,開始在Cygwin的命令列中執行編譯指令碼,但執行時報錯,因為sh指令碼還需要轉換成unix版本,於是在Cygwin中又裝了個dos2unix,將ijkplayer中的所有的sh指令碼全部轉換了一遍,然後再執行指令碼,在讀取一個配置檔案configure又出了問題,還是檔案格式問題,所以可以預見即使解決了這個檔案,後續可能還有一大堆的檔案存在這樣的問題,細思極恐,果斷棄坑。

Ubuntu下編譯IjkPlayer

棄坑Windows後又在電腦的虛擬機器上安裝了Ubuntu系統,可是等在Ubuntu上面搭建完Android開發環境後,發現硬碟空間不足了,不要問我為什麼空間不足,反正就是不足了,我能怎麼辦,我只能選擇原諒自己咯。後來想到我原來的一臺筆記本里面裝了Ubuntu 16.04,於是擦擦上面的灰,開機啟動。

搭建Android開發環境

1.安裝JDK

sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java8-installer
sudo apt-get install oracle-java8-set-default

//在命令列中使用java -version,java, javac命令不報命令找不到,就算安裝成功複製程式碼

2.安裝Android Studio

去官網或者國內站點下載Linux版本的Android Studio,這裡,我使用的是Android Studio3.0版本。下載完成後,將Android Studio解壓到/opt目錄,然後使用命令列執行Andorid Studio中的bin/studio.sh啟動Android Studio,進入嚮導介面,嚮導介面最後確認去下載SDK。下載完成後SDK路徑為/home/xxx(使用者名稱)/Android/Sdk。

3.下載NDK

使用Android Studio下載完SDK後,不用建立專案,直接開啟SDK Manager,在裡面去下載NDK,下載完成之後存放在sdk目錄下的ndk-bundle目錄,但這裡下載的NDK版本是比較新的版本16,而ijkplayer編譯也是不支援的,因為在ijkplayer\android\contrib\tools中有個sh指令碼會檢查編譯環境,其中有一段程式碼會檢查NDK版本:

case "$IJK_NDK_REL" in
    10e*)
        ........
        echo "IJK_NDK_REL=$IJK_NDK_REL"
        case "$IJK_NDK_REL" in
            11*|12*|13*|14*)
                if test -d ${ANDROID_NDK}/toolchains/arm-linux-androideabi-4.9
                then
                    echo "NDKr$IJK_NDK_REL detected"
                else
                    echo "You need the NDKr10e or later"
                    exit 1
                fi
            ;;
            *)
                echo "You need the NDKr10e or later"
                exit 1
            ;;
        esac
    ;;
esac複製程式碼

可以看出來這裡只支援10e,11,12,13,14,所以ndk版本低了不行,高了也不行,沒辦法,我們得去重新去官網下載低一點的版本,如r14b。

4.配置SDK和NDK路徑

找到/home/(使用者名稱)/目錄,使用快捷鍵Ctrl + H顯示隱藏檔案,找到.bashrc檔案開啟,配置自己的SDK和NDK路徑,例如:

export ANDROID_NDK=/home/leon/Android/andriod-ndk-r14b
export ANDROID_SDK=/home/leon/Android/Sdk
export PATH=$ANDROID_NDK:$ANDROID_SDK:$PATH複製程式碼

配置完成後,重啟命令列,輸入ndk-build命令,如果不報命令列找不到,說明NDK環境變數配置成功。

編譯IjkPlayer

Android環境搭建好後,就可以參考官方文件著手編譯ijkplayer了。

sudo apt-get update
sudo apt-get install git //安裝git
sudo apt-get install yasm //安裝yasm

sudo dpkg-reconfigure dash //在彈出提示框選擇“否”來使用bash

//下載ijkplayer到ijkplayer-android目錄
git clone https://github.com/Bilibili/ijkplayer.git ijkplayer-android
cd ijkplayer-android 

//使用預設配置
cd config
rm module.sh
ln -s module-lite.sh module.sh

cd ..
cd android/contrib
./compile-ffmpeg.sh clean //清理

cd ~/ijkplayer-android //返回原始碼根目錄
./init-android.sh //主要是去下載ffmpeg

cd android/contrib
./compile-ffmpeg.sh clean
./compile-ffmpeg.sh all //編譯ffmpeg,all是全部編譯,需要等待一段時間

cd .. //回到ijkplayer-android/android
./compile-ijk.sh all //編譯ijkplayer複製程式碼

編譯完成後,在android/ijkplayer目錄下各個庫模組當中找到生成的so庫:


既然so庫已經生成,就可以使用Andorid Studio再次開啟ijkplayer中的安卓示例專案(android/ijkplayer),執行後就可以播放示例視訊了。這個帶有so庫的示例專案我已上傳到Github,地址為github.com/uncleleonfa…,歡迎下載。

PLDroidPlayer

PLDroidPlayer 是七牛推出的一款適用於 Android 平臺的播放器 SDK,採用全自研的跨平臺播放核心,擁有豐富的功能和優異的效能,可高度定製化和二次開發。示例專案地址為:github.com/pili-engine…
PLDroidPlayer的整合要比ijkPlayer簡單很多,不用自己編譯so庫,不用自己建立SurfaceView和TextureView來播放視訊。可參考官方開發指南整合即可。

測試開發

為了保證測試的變數只是播放器引擎本身(這裡暫時將播放器引擎簡單的理解為各個播放器的MediaPlayer),我們定義一個公共的UI介面即VideoView來播放視訊流,然後通過代理模式去代理不同的播放器引擎。這樣VideoView在播放視訊時,可以通過代理使用不同的播放引擎(MediaPlayer)來播放。我們這裡主要測試播放器播放視訊首開的時間,播放器播放視訊過程中Cpu,記憶體的佔用情況。測試專案地址為:github.com/uncleleonfa…,測試專案執行效果:

IMediaPlayer

定義統一的MediaPlayer介面。

public interface IMediaPlayer {

    void prepareAsync() throws IllegalStateException;

    void start() throws IllegalStateException;

    void stop() throws IllegalStateException;

    void pause() throws IllegalStateException;

    void release();

    void reset();

    .......
}複製程式碼

IMeidaPlayerProxy

定義MeidaPlayer的代理介面,所有MediaPlayer的代理必須實現newInstance介面建立MediaPlayer。

interface IMediaPlayerProxy {
    IMediaPlayer newInstance();
}複製程式碼

IjkPlayer的MediaPlayer的代理

在使用IjkPlayer之前需要新增依賴,並且將編譯好的so庫新增到專案中的jniLibs下。

//新增ijkplayer依賴
dependencies {
    compile 'tv.danmaku.ijk.media:ijkplayer-java:0.8.4'
    compile 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.4'
    compile 'tv.danmaku.ijk.media:ijkplayer-x86:0.8.4'

}

//IjkMediaPlayer代理,實現IMediaPlayer介面
public class IjkMediaPlayerProxy implements IMediaPlayerProxy, IMediaPlayer {

    //宣告一個IjkMediaPlayer物件
    private IjkMediaPlayer mIjkMediaPlayer;

    @Override
    public IMediaPlayer newInstance() {
        //建立IjkMeidaPlayer物件
        mIjkMediaPlayer = new IjkMediaPlayer();
        return this;
    }

    @Override
    public void prepareAsync() throws IllegalStateException {
        mIjkMediaPlayer.prepareAsync();
    }

    @Override
    public void start() throws IllegalStateException {
        mIjkMediaPlayer.start();
    }

    @Override
    public void stop() throws IllegalStateException {
        mIjkMediaPlayer.stop();
    }
    ......
}複製程式碼

PLDroidPlayer的MediaPlayer代理

在使用PLMediaPlayer之前參考官方文件整合PLDroidPlayer

//PLMediaPlayer代理,實現IMediaPlayer介面
public class PLMediaPlayerProxy implements IMediaPlayerProxy, IMediaPlayer {

    //定義PLMediaPlayer物件
    private PLMediaPlayer mMediaPlayer;
    //AVOptions為MediaPlayer的選項配置,例如可以配置開啟硬解碼
    private AVOptions mAvOptions;


    @Override
    public IMediaPlayer newInstance() {
        //建立PLDroidPlayer的PLMediaPlayer物件
        mMediaPlayer = new PLMediaPlayer(mContext, mAvOptions);
        return this;
    }


    @Override
    public void prepareAsync() throws IllegalStateException {
        mMediaPlayer.prepareAsync();
    }

    @Override
    public void start() throws IllegalStateException {
        mMediaPlayer.start();
    }

    @Override
    public void stop() throws IllegalStateException {
        mMediaPlayer.stop();
    }

    ........
}複製程式碼

VideoView

VideoView仿照原生VideoView的實現,這裡主要修改的是MediaPlayer的邏輯,方便配置使用不同播放器的MediaPlayer。

public class VideoView extends SurfaceView implements IMediaPlayer.OnPreparedListener,
        IMediaPlayer.OnErrorListener,
        IMediaPlayer.OnCompletionListener,
        IMediaPlayer.OnInfoListener,
        IMediaPlayer.OnVideoSizeChangeListener{

    //定義MediaPlayer代理
    private IMediaPlayerProxy mMediaPlayerProxy;

    //定義VideoView使用的MediaPlayer
    private IMediaPlayer mMediaPlayer;


    //設定MediaPlayer的代理
    public void setMediaPlayerProxy(IMediaPlayerProxy mediaPlayerProxy) {
        mMediaPlayerProxy = mediaPlayerProxy;
    }

    //開啟視訊
    private void openVideo() {
        if (mVideoPath == null) {
            return;
        }
        release();
        //使用代理建立對應的MediaPlayer物件
        mMediaPlayer = mMediaPlayerProxy.newInstance();
        mMediaPlayer.setScreenOnWhilePlaying(true);
        mMediaPlayer.setDisplay(mSurfaceHolder);
        mMediaPlayer.setLogEnabled(BuildConfig.DEBUG);
        mMediaPlayer.setOnPreparedListener(this);
        mMediaPlayer.setOnInfoListener(this);
        mMediaPlayer.setOnCompletionListener(this);
        mMediaPlayer.setOnErrorListener(this);
        mMediaPlayer.setOnVideoSizeChangeListener(this);
        try {
            mMediaPlayer.setDataSource(mVideoPath);
            ......
            mMediaPlayer.prepareAsync();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onPrepared(IMediaPlayer iMediaPlayer) {
        iMediaPlayer.start();//開始播放
    }
}複製程式碼

LogUtils

LogUtils用於取樣cpu和記憶體資料,裡面使用ScheduledThreadPoolExecutor每隔1s取樣一次資料。

//開始取樣
public void start() {
    scheduler.scheduleWithFixedDelay(new SampleTask(), 0L, 1000L, TimeUnit.MILLISECONDS);
}
//停止取樣
public void stop() {
    scheduler.shutdown();
}

//取樣任務
private class SampleTask implements Runnable {

    @Override
    public void run() {
        float cpu = sampleCPU(); //取樣CPU使用
        float mem = sampleMemory(); //取樣記憶體使用
    }
}複製程式碼

LogView

LogView是列印Log的自定義控制元件,它由一個TextView和ScrollView組成,TextView在ScrollView內部,用來顯示log,ScrollView用來滾動。

public class LogView extends RelativeLayout {

    public LogView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        LayoutInflater.from(context).inflate(R.layout.view_log, this);

        final TextView textView = findViewById(R.id.tv);
        final ScrollView scrollView = findViewById(R.id.scroll_view);
        final StringBuilder stringBuilder = new StringBuilder();

        //監聽LogUtils的log
        LogUtils.getInstance().setOnUpdateLogListener(new LogUtils.OnUpdateLogListener() {
            @Override
            public void onUpdate(final long timestamp, final String msg) {
                //在主執行緒重新整理介面
                post(new Runnable() {
                    @Override
                    public void run() {
                        String dateString = mSimpleDateFormat.format(new Date(timestamp));
                        String log = dateString + ": " + msg + "\n";
                        //新增一行log
                        stringBuilder.append(log);
                        //設定log顯示
                        textView.setText(stringBuilder.toString());
                        //滾動ScrollView到底部
                        scrollView.fullScroll(View.FOCUS_DOWN);
                    }
                });

            }
        });
    }

}複製程式碼

測試

測試視訊流是:

//點播MP4視訊
String path = "http://hc.yinyuetai.com/uploads/videos/common/2B40015FD4683805AAD2D7D35A80F606.mp4?sc=364e86c8a7f42de3&br=783&rd=Android";
//HLS直播流
String path = "http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8";複製程式碼

在VieoView中,MediaPlayer開始準備播放之前,初始化LogUtils,埋點記錄MediaPlayer的準備時間。

try {
    //設定視訊源
    mMediaPlayer.setDataSource(mVideoPath);
    //初始化LogUtils
    LogUtils.getInstance().init(getContext());
    //記錄開始準備時間
    LogUtils.getInstance().onStartPrepare();
    mMediaPlayer.prepareAsync();
} catch (IOException e) {
    e.printStackTrace();
}複製程式碼

當MediaPlayer準備好後,會回撥onPrepared,再次記錄準備結束時間,這樣,準備結束時間減去準備開始時間就是MediaPlayer準備耗時,即我們的首開時間。

//準備好後的回撥
@Override
public void onPrepared(IMediaPlayer iMediaPlayer) {
    //記錄準備結束時間
    LogUtils.getInstance().onEndPrepare();
    //開始播放
    iMediaPlayer.start();
    //開始每隔1s取樣,播放結束後停止取樣,主要用於點播取樣
    LogUtils.getInstance().start();

    //開始每隔1s取樣,取樣5min,5min之後,自行停止,主要用於直播取樣
    //LogUtils.getInstance().startForDuration(5);
}

//播放結束
@Override
public void onCompletion(IMediaPlayer iMediaPlayer) {
    //播放結束,停止取樣
    LogUtils.getInstance().stop();
}複製程式碼

測試IjkPlayer

建立一個IjkPlayerActivity使用IjkMediaPlayer來播放視訊。

public class IjkPlayerActivity extends AppCompatActivity{

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ijkplayer);


        //初始化IjkPlayer
        IjkMediaPlayer.loadLibrariesOnce(null);
        IjkMediaPlayer.native_profileBegin("libijkplayer.so");

        VideoView videoView = findViewById(R.id.video_view);
        //設定IjkMediaPlayer代理
        videoView.setMediaPlayerProxy(new IjkMediaPlayerProxy());
        String path = "視訊url"
        //設定視訊url
        videoView.setVideoPath(path);
    }


    @Override
    protected void onStop() {
        super.onStop();
        //通知IjkMediaPlayer結束
        IjkMediaPlayer.native_profileEnd();
    }

}複製程式碼

另外,在VideoView初始化MediaPlayer時,可以呼叫enableMediaCodec()來開啟IjkPlayer的硬解碼:

private void openVideo() {
        .......
        mMediaPlayer.enableMediaCodec();
 }複製程式碼

測試PLDroidPlayer

建立一個PLDroidPlayerActivity使用PLMediaPlayer來播放視訊。

public class PLDroidPlayerActivity extends AppCompatActivity{

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pldroid);

        VideoView mVideoView = findViewById(R.id.video_view);

        //配置AVoptions來開啟硬編碼,預設為軟編碼
        AVOptions avOptions = new AVOptions();
        avOptions.setInteger(AVOptions.KEY_MEDIACODEC, AVOptions.MEDIA_CODEC_HW_DECODE);

        mVideoView.setMediaPlayerProxy(new PLMediaPlayerProxy(this, avOptions));
        String path = "視訊url";
        mVideoView.setVideoPath(path);
    }

}複製程式碼

測試結果

Round 1

  • 機型:Moto G (2代)
  • 系統版本:5.1
  • 資料來源:String path = "ivi.bupt.edu.cn/hls/cctv1hd…";
  • 軟硬編碼:軟解碼
  • 取樣時長:5min
  • IjkPlayer版本:0.8.4
  • PLDroid版本:2.0.3

IjkPlayer結果

  • 首開時間:1559ms
  • CPU佔比最小值:5.00%
  • CPU佔比最大值:80.72%
  • 平均CPU佔比:30.69%
  • 記憶體使用最小值:63.49MB
  • 記憶體使用最大值:114.92MB
  • 平均記憶體:110.19MB

PLDroidPlayer結果

  • 首開時間:198ms
  • CPU佔比最小值:3.11%
  • CPU佔比最大值:67.18%
  • 平均CPU佔比:30.25%
  • 記憶體使用最小值:32.34MB
  • 記憶體使用最大值:93.47MB
  • 平均記憶體:87.41MB

Round 2

  • 機型:Moto G (2代)
  • 系統版本:5.1
  • 資料來源:String path = "ivi.bupt.edu.cn/hls/cctv1hd…";
  • 軟硬編碼:硬解碼
  • 取樣時長:5min
  • IjkPlayer版本:0.8.4
  • PLDroid版本:2.0.3

IjkPlayer結果

  • 首開時間:2280ms
  • CPU佔比最小值:1.36%
  • CPU佔比最大值:17.37%
  • 平均CPU佔比:10.10%
  • 記憶體使用最小值:45.37MB
  • 記憶體使用最大值:52.34MB
  • 平均記憶體:48.81MB

PLDroidPlayer結果

  • 首開時間:174ms
  • CPU佔比最小值:2.00%
  • CPU佔比最大值:69.23%
  • 平均CPU佔比:28.00%
  • 記憶體使用最小值:30.98MB
  • 記憶體使用最大值:85.87MB
  • 平均記憶體:81.67MB

相關文章