草庐IT

如何基于 ZEGO SDK 实现 Android 通话质量监测

ZEGO即构开发者 2023-03-28 原文

功能简介

在进行视频通话过程中,用户有时候会出现网络不好的情况,比如在进行多人视频通话或者多人唱歌时,我们需要实时显示用户的网络质量。

示例源码

参考 下载示例源码 获取源码。

相关源码请查看 “/ZegoExpressExample/AdvancedStreaming/src/main/java/im/zego/streammonitoring” 目录下的文件。

前提条件

在监测通话质量之前,请确保:

基础网络质量报告

可以通过监听 onNetworkQuality 回调,收到房间内用户(包括自己)的上下行网络质量。此回调每隔两秒会收到一次,网络质量等级请参考 ZegoStreamQualityLevel

不同版本的 onNetworkQuality 回调逻辑有所不同:

  • 对于使用 2.14.0 及以上版本 ZEGO Express SDK 的用户,onNetworkQuality 回调的逻辑为:

    • 只要推流或者拉流,就能收到自己的网络质量回调。
    • 当拉取了其他用户推送的音视频流并且该用户在房间内时,才会收到该用户的网络质量回调。
    • 当 “userID” 为 “null” 时,代表本次是自己的网络质量,当 “userID” 不为 “null” 时,代表是房间内其他用户的报告。
  • 对于使用 2.10.02.13.1 版本 ZEGO Express SDK 的用户,onNetworkQuality 回调的逻辑为:

    • 必须既推流又拉流,才会收到自身的网络质量回调。
    • 当拉取一条流时,推送该条流的用户必须在同一房间内,且他也进行了拉流,才会收到该用户的网络质量回调。
    • 当 “userID” 为 “null” 时,代表本次是自己的网络质量,当 “userID” 不为 “null” 时,代表是房间内其他用户的报告。

onNetworkQuality 不适用于使用 CDN 进行直播的场景,可以参考 进阶质量报告 - 推流质量报告 监测 CDN 的推流质量。

public void setEngineEventHandler(){
        engine.setEventHandler(new IZegoEventHandler() {
            @Override
            public void onNetworkQuality(String userID, ZegoStreamQualityLevel upstreamQuality, ZegoStreamQualityLevel downstreamQuality) {
                super.onNetworkQuality(userID, upstreamQuality, downstreamQuality);
                if (userID == null) {
                    // 代表本地用户(我)的网络质量
                    //("我的上行网络质量是 %lu", (unsigned long)upstreamQuality);
                    //("我的下行网络质量是 %lu", (unsigned long)downstreamQuality);
                } else {
                    //代表房间内其他用户的网络质量
                    //("用户 %s 的上行网络质量是 %lu", userID, (unsigned long)upstreamQuality);
                    //("用户 %s 的下行网络质量是 %lu", userID, (unsigned long)downstreamQuality);
                }

                /*
                ZegoStreamQualityLevel.EXCELLENT, 网络质量极好
                ZegoStreamQualityLevel.GOOD, 网络质量好
                ZegoStreamQualityLevel.MEDIUM, 网络质量正常
                ZegoStreamQualityLevel.BAD, 网络质量差
                ZegoStreamQualityLevel.DIE, 网络异常
                ZegoStreamQualityLevel.UNKNOWN, 网络质量未知
                */

            }
        });
           
    }

进阶质量报告

如果上述的基础网络质量报告不能满足需求,ZEGO 还提供了更详细的推流质量报告、拉流质量报告以及其他相关信息。

推流质量报告

推流质量报告指用户把音视频推送到 ZEGO 服务端这个过程的质量报告,包含了采集、编码阶段音视频流的帧率,传输(发送)的音视频流的帧率、码率、延时及丢包率。

可以通过注册 onPublisherQualityUpdate 接收推流质量回调,推流成功后每隔三秒会收到此回调。可根据 quality(ZegoPublishStreamQuality) 参数实时了解推送的音视频流的健康情况。

  • 大多数情况下,只需关注 “quality” 的 “level” 参数,以 “level” 枚举值来判断推流的综合质量,详情可参考 ZegoStreamQualityLevel
  • 如果想关注更详细的推流质量参数,可以参考 ZegoPublishStreamQuality
    engine.setEventHandler(new IZegoEventHandler() {

            // 开发者可以在此回调中监控具体的质量以上报到业务服务器做监控,或者监控质量对象的某个字段以给用户友好的提示
            @Override
            public void onPublisherQualityUpdate(String streamID, ZegoPublishStreamQuality quality) {
                String networkQuality = "";
                // level 代表了推流质量的综合分数,大部分情况下,开发者可以参考此分数展示上行网络的质量

                switch (quality.level) {
                    case EXCELLENT:
                        networkQuality = "非常好";
                        break;
                    case GOOD:
                        networkQuality = "好";
                        break;
                    case MEDIUM:
                        networkQuality = "一般";
                        break;
                    case BAD:
                        networkQuality = "差";
                        break;
                    case DIE:
                        networkQuality = "失败";
                        break;
                    case UNKNOWN:
                        networkQuality = "未知";
                        break;
                    default:
                        break;
                }
                //("网络质量是:%s", networkQuality);
            }
        });

拉流质量报告

拉流质量报告指用户拉取播放音视频流这个过程的质量报告,包含了接收的音视频流的帧率、码率、延时和丢包率,解码阶段音视频流的帧率,以及渲染阶段的帧率、卡顿率、音视频整体质量。

可以通过注册 onPlayerQualityUpdate 接收拉流质量回调,拉流成功后每隔三秒会收到此回调。开发者可根据 quality(ZegoPlayStreamQuality) 参数实时了解拉取的音视频流的健康情况。

  • 大多数情况下,只需关注 “quality” 的 “level” 参数,以 “level” 枚举值来判断拉流的综合质量,详情可参考 ZegoStreamQualityLevel
  • 如果想关注更详细的拉流质量参数,可以参考 ZegoPlayStreamQuality
    engine.setEventHandler(new IZegoEventHandler() {
            // 开发者可以在此回调中监控具体的质量以上报到业务服务器做监控,或者监控质量对象的某个字段以给用户友好的提示
            @Override
            public void onPlayerQualityUpdate(String streamID, ZegoPlayStreamQuality quality) {
                String networkQuality = "";
                // level 代表了拉流质量的综合分数,大部分情况下,开发者可以参考此分数展示下行网络的质量

                switch (quality.level) {
                    case EXCELLENT:
                        networkQuality = "非常好";
                        break;
                    case GOOD:
                        networkQuality = "好";
                        break;
                    case MEDIUM:
                        networkQuality = "一般";
                        break;
                    case BAD:
                        networkQuality = "差";
                        break;
                    case DIE:
                        networkQuality = "失败";
                        break;
                    case UNKNOWN:
                        networkQuality = "未知";
                        break;
                    default:
                        break;
                }
                //("网络质量是:%s", networkQuality);
            }
        });
}

MOS 音质评分

ZEGO Express SDK 2.16.0 版本开始,拉流质量回调 onPlayerQualityUpdate 中新增 "mos" 字段,表示对拉流音质的评分。开发者对音频质量比较关注时,可通过该字段了解当前音频的质量情况。

mos 字段的取值范围为 [-1, 5],其中 -1 表示未知(例如异常拉流时无法评分),[0, 5] 表示评分。实时音频 MOS 评分对应的主观音质感受如下:

MOS 值 评价标准
4.0~5.0 音质很好,清晰流畅,听的清楚。
3.5~4.0 音质较好,偶有音质损伤,但依然清晰流畅,听的清楚。
3.0~3.5 音质一般,偶有卡顿,需要一点注意力才能听清。
2.5~3.0 音质较差,卡顿频繁,需要集中注意力才能听清。
2.0~2.5 音质很差,部分语义丢失,难以交流。
小于 2.0 音质极差,大量语义丢失,无法交流。
-1 未知。

其他信息监测

推流/拉流状态变化通知

1. 推流状态回调

在推流成功后,可以通过 onPublisherStateUpdate 获取推流状态变更的通知。

engine.setEventHandler(new IZegoEventHandler() {
    @Override
    public void onPublisherStateUpdate(String streamID, ZegoPublisherState state, int errorCode, JSONObject extendedData) {
        super.onPublisherStateUpdate(streamID, state, errorCode, extendedData);
        // 当 state 为 PUBLISHER_STATE_NO_PUBLISH 时,且 errcode 非 0,表示推流失败,同时不会再进行重试推流了,此时可在界面作出推流失败提示;    
        // 当 state 为 PUBLISHER_STATE_PUBLISH_REQUESTING 时,且 errcode 非 0,表示在重试推流,此时如果超出重试时间未成功推流会抛出推流失败通知。
    }
}

可以根据回调内的 “state” 参数是否在 “正在请求推流状态” 来大体判断用户的推流网络情况。“state” 参数的取值与用户推流状态对应如下:

枚举值 说明
ZegoPublisherState.NO_PUBLISH 未推流状态,在推流前处于该状态。如果推流过程出现稳态的异常,例如 AppID 或 Token 不正确,或者如果其他用户已经在推送流,推送相同流 ID 的流会失败,都会进入未推流状态。
ZegoPublisherState.PUBLISH_REQUESTING 正在请求推流状态,推流操作执行成功后会进入正在请求推流状态,通常通过该状态进行 UI 界面的展示。如果因为网络质量不佳产生的中断,SDK 会进行内部重试,也会回到正在请求推流状态。
ZegoPublisherState.PUBLISHING 正在推流状态,进入该状态表明推流已经成功,用户可以正常通信。

参数 “extendedData” 为状态更新附带的扩展信息。若使用 ZEGO 的 CDN 内容分发网络,在推流成功后,该参数的内容的键为 “flv_url_list”、“rtmp_url_list”、“hls_url_list”,分别对应 flv、rtmp、hls 协议的拉流 URL。

2. 拉流状态变更回调

在拉流成功后,开发者可通过 onPlayerStateUpdate 获取推流状态变更的通知。

engine.setEventHandler(new IZegoEventHandler() {
    @Override
    public void onPlayerStateUpdate(String streamID, ZegoPlayerState state, int errorCode, JSONObject extendedData) {
        super.onPlayerStateUpdate(streamID, state, errorCode, extendedData);
        // 当 state 为 PLAYER_STATE_NO_PLAY 时,且 errcode 非 0,表示拉流失败,同时不会再进行重试拉流了,此时可在界面作出拉流失败提示;
        // 当 state 为 PLAYER_STATE_PLAY_REQUESTING 时,且 errcode 非 0,表示重试拉流,此时如果超出重试时间未成功拉到流会抛出拉流失败通知。
    }
}

开发者可根据 “state” 参数是否在 “正在请求拉流状态” 来大体判断用户的拉流网络情况。“state” 参数的取值与用户拉流状态对应如下:

枚举值 说明
ZegoPlayerState.NO_PLAY 未拉流状态,在拉流前处于该状态。如果拉流过程出现稳态的异常,例如 AppID 或 Token 不正确,都会进入未拉流状态。
ZegoPlayerState.PLAY_REQUESTING 正在请求拉流状态,拉流操作执行成功后会进入正在请求拉流状态,通常通过该状态进行应用界面的展示。如果因为网络质量不佳产生的中断,SDK 会进行内部重试,也会回到正在请求拉流状态。
ZegoPlayerState.PLAYING 正在拉流状态,进入该状态表明拉流已经成功,用户可以正常通信。

接收到音频/视频首帧的通知

1. 推流端音频采集首帧回调

可以通过注册 onPublisherCapturedAudioFirstFrame 接收音频首帧回调。调用推流接口成功后,SDK 采集到第一帧音频数据时会收到此回调。

在未推流或未预览的情况下,首次推流或首次预览,即 SDK 内部的音视频模块的引擎启动时,会去采集本机设备的音频数据,会收到该回调。开发者可根据该回调判断 SDK 是否真的采集到音频数据,若未收到该回调,说明音频采集设备被占用或异常。

engine.setEventHandler(new IZegoEventHandler() {
    @Override
    public void onPublisherCapturedAudioFirstFrame() {
        super.onPublisherCapturedAudioFirstFrame();
    }
}

2. 推流端视频采集首帧回调

可以通过注册 onPublisherCapturedVideoFirstFrame 接收视频首帧回调。调用推流接口成功后,SDK 采集到第一帧视频数据时会收到此回调。

在未推流或未预览的情况下,首次推流或首次预览,即 SDK 内部的音视频模块的引擎启动时,会去采集本机设备的视频数据,会收到该回调。可以根据该回调判断 SDK 是否真的采集到视频数据,若未收到该回调,说明视频采集设备被占用或异常。

engine.setEventHandler(new IZegoEventHandler() {
    @Override
    public void onPublisherCapturedVideoFirstFrame(ZegoPublishChannel channel) {
    super.onPublisherCapturedVideoFirstFrame(channel);
    }
}

3. 拉流端音频接收首帧回调

开发者可通过注册 onPlayerRecvAudioFirstFrame 监听拉流端音频接收首帧回调。调用拉流接口成功后,SDK 拉流拉到第一帧音频数据时会收到此回调。

engine.setEventHandler(new IZegoEventHandler() {
    @Override
    public void onPlayerRecvAudioFirstFrame(String streamID) {
        super.onPlayerRecvAudioFirstFrame(streamID);
        AppLogger.getInstance().receiveCallback("onPlayerRecvAudioFirstFrame streamID:%s",streamID);
    }
}

4. 拉流端视频接收首帧回调

可以通过注册 onPlayerRecvVideoFirstFrame 监听拉流端接收视频首帧回调。调用拉流接口成功后,SDK 拉流拉到第一帧视频数据时会收到此回调。

engine.setEventHandler(new IZegoEventHandler() {
    @Override
    public void onPlayerRecvVideoFirstFrame(String streamID) {
        super.onPlayerRecvVideoFirstFrame(streamID);
    }
}

5. 拉流端渲染完视频首帧回调

可以通过注册 onPlayerRenderVideoFirstFrame 监听拉流端渲染完视频首帧回调。调用拉流接口成功后,SDK 拉流并渲染完第一帧视频数据后会收到此回调。

可以用该回调来统计首帧耗时或更新播放流的 UI 组件。

engine.setEventHandler(new IZegoEventHandler() {
    @Override
    public void onPlayerRenderVideoFirstFrame(String streamID){
        super.onPlayerRenderVideoFirstFrame(streamID);
    }
}

视频分辨率变化的回调

1. 采集视频分辨率变更回调

可以通过注册 onPublisherVideoSizeChanged 监听采集视频大小变更回调。推流成功后,在推流中途如果视频采集分辨率发生变化将会收到此回调。

当在未推流或未预览的情况下,首次推流或首次预览,即 SDK 内部的音视频模块的引擎启动时,会去采集本机设备的视频数据,此时采集分辨率会改变。

可以根据此回调来去除本地预览的 UI 的遮盖等类似操作。也可以根据该回调的分辨率来动态调整预览视图的比例等。

engine.setEventHandler(new IZegoEventHandler() {
    @Override
    public void onPublisherVideoSizeChanged(int width, int height, ZegoPublishChannel channel) {
        super.onPublisherVideoSizeChanged(width, height, channel);
    }
}

2. 拉流分辨率变更通知

您可以通过注册 onPlayerVideoSizeChanged 获取拉流分辨率变更通知。拉流成功后,在拉流中途如果有视频分辨率发生变化将会收到此回调,用户可根据流的最终分辨率调整显示。

  • 若拉的流只有音频数据,会收不到该回调。
  • 若推流端由于网络问题触发 SDK 内部的流量控制时,可能会动态减小推流端的编码分辨率,此时也会收到此回调。所拉的音视频流真正渲染到所设置 UI 播放界面时会触发此回调。开发者可利用该回调通知来更新或切换真正播放流的 UI 组件。
engine.setEventHandler(new IZegoEventHandler() {
    @Override
    public void onPlayerVideoSizeChanged(String streamID, int width, int height) {
    }
}

API 参考列表

方法 描述
onPublisherQualityUpdate 推流质量回调
onPlayerQualityUpdate 拉流质量更新回调
onPublisherStateUpdate 推流状态回调
onPlayerStateUpdate 拉流状态回调
onPublisherCapturedAudioFirstFrame 推流端音频采集首帧回调
onPublisherCapturedVideoFirstFrame 推流端视频采集首帧回调
onPlayerRecvAudioFirstFrame 拉流端音频接收首帧回调
onPlayerRecvVideoFirstFrame 拉流端视频接收首帧回调
onPlayerRenderVideoFirstFrame 拉流端渲染完视频首帧回调
onPublisherVideoSizeChanged 采集视频大小变更回调
onPlayerVideoSizeChanged 拉流分辨率变更通知
onPublisherRelayCDNStateUpdate 添加/删除转推 CDN 地址状态回调
onPlayerRecvSEI 收到远端流的 SEI 内容

获取Demo

获取本文的Demo、开发文档、技术支持。
获取SDK的商务活动、热门产品。
注册即构ZEGO开发者帐号,快速开始。

有关如何基于 ZEGO SDK 实现 Android 通话质量监测的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  4. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  5. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  6. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  7. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  8. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

    在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

  9. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  10. ruby - 如何使用文字标量样式在 YAML 中转储字符串? - 2

    我有一大串格式化数据(例如JSON),我想使用Psychinruby​​同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解

随机推荐