接入或使用客户端诊断能力
在客户端 App 中预留诊断入口,可以让测试人员或获得用户授权的支持人员在问题发生后立即上传 SDK 日志;遇到无声、卡顿、花屏、黑屏或对讲异常时,还可以采集当时的原始音视频数据并随日志上传。
建议在开发阶段就完成日志上传入口。这样在需要平台协助诊断时,可以直接上传同一次复现的日志。原始音视频采集涉及更高的数据敏感度,可以根据产品的支持需求按需接入。
这些诊断接口用于客户端 SDK。设备端日志需要按接入 C SDK中的方式由设备保存或回传。
选择诊断方式
根据是否需要把诊断功能接入自己的客户端选择方式:
| 诉求 | 使用方式 |
|---|---|
| 在自己的 App 中长期保留诊断入口 | 接入 SDK 提供的诊断接口,由 App 控制采集与上传 |
| 临时复现并把数据交给技术支持,不修改自己的 App | 直接使用预编译 TiRTC Flutter Android Demo,在 Demo 中连接设备、复现问题并上传 |
两种方式最终都会返回日志 ID。普通日志不包含原始音视频;原始音视频诊断数据是 SDK 在播放和对讲过程中记录的音视频数据,可能仍是编码数据,不是可以直接播放的 MP4,也不能替代业务录制。诊断数据可能包含真实音视频内容,采集和上传前必须取得必要授权,并避免录入与问题无关的敏感内容。
在自己的 App 中接入诊断能力
接入日志上传(推荐)
日志上传主要用于排查问题,不建议把入口作为面向所有用户的常规功能直接展示。常见做法是在“关于”页、版本号或其他约定位置连续点击、双击或长按,打开一个诊断页面;企业内部版本也可以放在仅测试人员可见的设置项中。
建议诊断页面显示“上传中”状态;上传成功后展示并允许复制 logId,上传失败时展示错误码并允许重试。隐藏入口只是一种交互设计,不是安全控制;应用仍需按照隐私政策告知数据用途,并限制能够触发诊断操作的用户范围。
完成 SDK 初始化后,调用当前客户端 SDK 的日志上传接口:
final ({int code, String? logId}) result = await TiRtcLogging.upload();
if (result.code == 0 && result.logId != null) {
// 在诊断页面展示并允许复制 result.logId。
}const result = await TiRtcLogging.upload();
if (result.code === 0 && result.logId !== null) {
// 在诊断页面展示并允许复制 result.logId。
}val accepted = TiRtcLogging.upload { code, logId ->
if (code == 0 && !logId.isNullOrEmpty()) {
// 在诊断页面展示并允许复制 logId。
}
}
// accepted == 0 只表示上传任务已开始,最终结果以回调为准。let accepted = TiRtcLogging.upload { result in
if result.succeeded, let logId = result.logId {
// 在诊断页面展示并允许复制 logId。
}
}
// accepted == 0 只表示上传任务已开始,最终结果以 completion 为准。const result = await TiRtcLogging.upload();
if (result.code === 0 && result.logId !== undefined) {
// 在诊断页面展示并允许复制 result.logId。
}log_id = client.upload_logs()
if log_id:
# 在诊断页面展示并允许复制 log_id。
pass问题复现后尽快上传,避免后续运行覆盖相关记录。上传成功后,把 logId、问题发生时间、SDK 版本和最短复现步骤一并提供给技术支持。完整的失败处理和排查材料要求见上传客户端日志。
按需接入原始音视频采集
原始音视频诊断采集接口可以记录播放时收到的音视频,帮助技术支持定位普通日志无法解释的媒体问题。代码示例提供上行音频选项时,还可以记录对讲时发送的音频。
建议把它作为诊断页面中的增强操作:先由用户或支持人员明确选择要采集的 Stream ID,再开始和停止采集,不要在应用启动后默认采集。普通日志不包含原始音视频;只有应用显式完成一次采集后,下一次日志上传才会附带采集文件。
支持采集两类数据:
- 播放音视频时,设备发送、客户端收到的下行原始编码数据。
- 对讲时,客户端麦克风采集并发送给设备的上行音频。
调用采集接口
开始前确认连接已经建立,并取得实际使用的下行音频和下行视频 Stream ID;代码示例包含上行音频选项时,再填写本地对讲 Stream ID。至少填写一个实际要采集的 Stream ID 列表;Stream ID 取值为 0..15。
final started = await conn.startRawDump(
TiRtcRawDumpOptions(
audioStreamIds: <int>[10],
videoStreamIds: <int>[11],
uplinkAudioStreamIds: <int>[14],
),
);
if (!started.success || started.data == null) return;
// 复现问题后停止采集。
final stopped = await started.data!.stop();
if (!stopped.success || stopped.data == null) return;
if (!stopped.data!.captureComplete) {
debugPrint('media capture incomplete: ${stopped.data!.stopReason}');
}
final upload = await TiRtcLogging.upload();
// upload.code == 0 且 logId 非空时提交给技术支持。const started = await conn.startRawDump({
audioStreamIds: [10],
videoStreamIds: [11],
uplinkAudioStreamIds: [14],
});
if (!started.success || started.data === null) return;
// 复现问题后停止采集。
const stopped = await started.data.stop();
if (!stopped.success || stopped.data === null) return;
if (!stopped.data.captureComplete) {
console.warn(`media capture incomplete: ${stopped.data.stopReason}`);
}
const upload = await TiRtcLogging.upload();
// upload.code === 0 且 logId 非空时提交给技术支持。val started = conn.startRawDump(
TiRtcRawDumpOptions(
audioStreamIds = intArrayOf(10),
videoStreamIds = intArrayOf(11),
uplinkAudioStreamIds = intArrayOf(14),
),
)
val capture = started.dump ?: return
// 复现问题后停止采集。
val stopped = capture.stop()
val captureFile = stopped.archive ?: return
if (!captureFile.captureComplete) {
Log.w("TiRTC", "media capture incomplete: ${captureFile.stopReason}")
}
TiRtcLogging.upload { code, logId ->
// code == 0 且 logId 非空时提交给技术支持。
}let started = await conn.startRawDump(
options: TiRtcRawDumpOptions(
audioStreamIds: [10],
videoStreamIds: [11],
uplinkAudioStreamIds: [14]
)
)
guard let capture = started.dump else { return }
// 复现问题后停止采集。
let stopped = await capture.stop()
guard let captureFile = stopped.archive else { return }
if !captureFile.captureComplete {
print("media capture incomplete: \(captureFile.stopReason)")
}
TiRtcLogging.upload { result in
// result.succeeded 时提交 result.logId。
}const started = await conn.startRawDump({
audioStreamIds: [10],
videoStreamIds: [11],
uplinkAudioStreamIds: [14],
});
if (!started.success || started.data === null) return;
// 复现问题后停止采集。
const stopped = await started.data.stop();
if (!stopped.success || stopped.data === null) return;
if (!stopped.data.captureComplete) {
console.warn(`media capture incomplete: ${stopped.data.stopReason}`);
}
const upload = await TiRtcLogging.upload();
// upload.code === 0 且 logId 非空时提交给技术支持。options = tirtc.RawDumpOptions(
audio_stream_ids=(10,),
video_stream_ids=(11,),
)
with conn.start_raw_dump(options) as capture:
# 复现问题后停止采集。
archive = capture.stop()
if not archive.capture_complete:
print(f"media capture incomplete: {archive.stop_reason}")
log_id = client.upload_logs()
# 把 log_id 提交给技术支持。单次采集最长 5 分钟,采集文件上限为 1 GiB;达到限制、来源关闭、资源不足或写入失败时会自动停止。停止成功后,仍需检查采集是否完整、停止原因以及未保存的包数和字节数;结果标记为空表示没有采到匹配的数据。通常先检查填写的 Stream ID 是否与实际发送和订阅一致。
TiRTC 和 Ti 云存共用同一套诊断采集资源,同一进程同时只能进行一个原始音视频采集任务。先停止采集,再上传日志;采集和上传并发时会返回资源正在使用。下一次成功采集或成功上传可能清理旧采集文件,需要长期保留时,在开始下一次采集或上传前把文件复制到应用目录。上传失败时,采集文件保留在 SDK 缓存目录,可在网络恢复后重试;成功上传后 SDK 会删除该文件。
使用预编译 Flutter Android Demo 采集
Demo 已经集成原始音视频诊断采集和日志上传,不需要修改客户端代码。它会把采集数据和运行日志一起上传,并返回日志 ID,供技术支持分析。
开始前准备
- 一台 Android 手机和可以复现问题的设备。
- TiRTC
app_id、设备的remote_id和连接 Token。可以通过 TiRTC DevTools CLI生成一次性 Token,也可以启动 Token 签发 HTTP 服务,让手机在连接前动态获取 Token。 - 设备实际发送的音频、视频 Stream ID。
- 排查对讲时,还要确认设备订阅的对讲 Stream ID,以及设备是否需要先收到业务控制命令。
Stream ID 标识同一条连接、一个发送方向中的一路流,取值范围是 0..15。同一方向、同一路流的发送端编号必须与接收端绑定和订阅一致;同一发送方向内,不同音视频流使用不同编号,音频和视频不能共用编号。相反方向使用独立的编号空间,可以复用相同数值。因此,采集问题时要分别记录“设备端 → 客户端”的下行音视频 Stream ID 和“客户端 → 设备端”的对讲 Stream ID,不要只记录一个未标方向的数字。
前往下载 API 示例,在 Flutter 卡片中下载并安装 Android APK。
单次最多采集 5 分钟。
连接设备
- 打开 Demo,选择
Ti RTC。 - 在「连接」中填写
app_id和remote_id。除非技术支持提供了专用地址,否则endpoint留空。 - 在「鉴权」中填写「一次性连接 Token」,或者在「TiRTC DevTools 服务地址」中填写 CLI 输出的「Token 签发服务地址」。
- 在「媒体」中填写设备实际发送的音频和视频 Stream ID。最多可以填写 3 路视频;不需要接收的媒体类型留空。
- 点击「开始连接、拉流播放」,确认已经连接到目标设备并进入播放页。
采集下行音视频
- 确认设备正在发送需要分析的音频或视频。
- 点击画面左侧的圆形「抓数据」按钮。按钮变为「结束上传」后,按实际操作复现问题。
- 问题出现后点击「结束上传」。按钮会依次显示「打包中」和「上传中」。
- 出现「日志上传成功」对话框后,复制日志 ID。
配置的下行 Stream ID 必须与设备发送时使用的编号一致。编号不一致时,即使连接成功,Demo 也收不到或采集不到对应数据。
采集对讲音频
对讲通常在播放页进行。采集期间,Demo 会同时记录当前的下行音视频和 Android 手机麦克风的上行音频。
- 打开「偏好设置」,确认「传输 Stream ID」与设备订阅的对讲 Stream ID 一致。
- 返回播放页,点击「抓数据」。
- 如果设备收到业务控制命令后才订阅对讲流,点击右上角的「命令」,发送双方约定的命令。设备建立连接后已经订阅时,跳过此步。
- 点击「启动麦克风」,并在首次使用时允许麦克风权限。
- 按实际操作复现问题。需要同时分析下行数据时,让设备继续发送对应的音频或视频。
- 点击「结束上传」,等待上传完成并复制日志 ID。
需要控制命令时,Demo 不会自动发送。命令 ID、内容格式和内容由设备业务协议定义,具体操作见收发命令消息。
上传失败后重试
上传失败时,采集数据会保留在手机上。网络恢复后点击「重试上传」,无需重新复现。
提交排查信息
联系技术支持时,请提供:
- 日志 ID。
- 问题现象、最短复现步骤和大致发生时间。
- 设备型号,以及本次使用的下行音视频 Stream ID、传输 Stream ID 和编码格式。
不要在工单、聊天记录或截图中提交连接 Token、SecretKeyId、device_secret_key 等敏感信息。