Skip to content

接入或使用客户端诊断能力

在客户端 App 中预留诊断入口,可以让测试人员或获得用户授权的支持人员在问题发生后立即上传 SDK 日志;遇到无声、卡顿、花屏、黑屏或对讲异常时,还可以采集当时的原始音视频数据并随日志上传。

建议在开发阶段就完成日志上传入口。这样在需要平台协助诊断时,可以直接上传同一次复现的日志。原始音视频采集涉及更高的数据敏感度,可以根据产品的支持需求按需接入。

这些诊断接口用于客户端 SDK。设备端日志需要按接入 C SDK中的方式由设备保存或回传。

选择诊断方式

根据是否需要把诊断功能接入自己的客户端选择方式:

诉求使用方式
在自己的 App 中长期保留诊断入口接入 SDK 提供的诊断接口,由 App 控制采集与上传
临时复现并把数据交给技术支持,不修改自己的 App直接使用预编译 TiRTC Flutter Android Demo,在 Demo 中连接设备、复现问题并上传

两种方式最终都会返回日志 ID。普通日志不包含原始音视频;原始音视频诊断数据是 SDK 在播放和对讲过程中记录的音视频数据,可能仍是编码数据,不是可以直接播放的 MP4,也不能替代业务录制。诊断数据可能包含真实音视频内容,采集和上传前必须取得必要授权,并避免录入与问题无关的敏感内容。

在自己的 App 中接入诊断能力

接入日志上传(推荐)

日志上传主要用于排查问题,不建议把入口作为面向所有用户的常规功能直接展示。常见做法是在“关于”页、版本号或其他约定位置连续点击、双击或长按,打开一个诊断页面;企业内部版本也可以放在仅测试人员可见的设置项中。

建议诊断页面显示“上传中”状态;上传成功后展示并允许复制 logId,上传失败时展示错误码并允许重试。隐藏入口只是一种交互设计,不是安全控制;应用仍需按照隐私政策告知数据用途,并限制能够触发诊断操作的用户范围。

完成 SDK 初始化后,调用当前客户端 SDK 的日志上传接口:

dart
final ({int code, String? logId}) result = await TiRtcLogging.upload();
if (result.code == 0 && result.logId != null) {
  // 在诊断页面展示并允许复制 result.logId。
}
tsx
const result = await TiRtcLogging.upload();
if (result.code === 0 && result.logId !== null) {
  // 在诊断页面展示并允许复制 result.logId。
}
kotlin
val accepted = TiRtcLogging.upload { code, logId ->
  if (code == 0 && !logId.isNullOrEmpty()) {
    // 在诊断页面展示并允许复制 logId。
  }
}
// accepted == 0 只表示上传任务已开始,最终结果以回调为准。
swift
let accepted = TiRtcLogging.upload { result in
    if result.succeeded, let logId = result.logId {
        // 在诊断页面展示并允许复制 logId。
    }
}
// accepted == 0 只表示上传任务已开始,最终结果以 completion 为准。
ts
const result = await TiRtcLogging.upload();
if (result.code === 0 && result.logId !== undefined) {
  // 在诊断页面展示并允许复制 result.logId。
}
python
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

dart
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 非空时提交给技术支持。
tsx
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 非空时提交给技术支持。
kotlin
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 非空时提交给技术支持。
}
swift
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。
}
ts
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 非空时提交给技术支持。
python
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 分钟。

连接设备

  1. 打开 Demo,选择 Ti RTC
  2. 在「连接」中填写 app_idremote_id。除非技术支持提供了专用地址,否则 endpoint 留空。
  3. 在「鉴权」中填写「一次性连接 Token」,或者在「TiRTC DevTools 服务地址」中填写 CLI 输出的「Token 签发服务地址」。
  4. 在「媒体」中填写设备实际发送的音频和视频 Stream ID。最多可以填写 3 路视频;不需要接收的媒体类型留空。
  5. 点击「开始连接、拉流播放」,确认已经连接到目标设备并进入播放页。

采集下行音视频

  1. 确认设备正在发送需要分析的音频或视频。
  2. 点击画面左侧的圆形「抓数据」按钮。按钮变为「结束上传」后,按实际操作复现问题。
  3. 问题出现后点击「结束上传」。按钮会依次显示「打包中」和「上传中」。
  4. 出现「日志上传成功」对话框后,复制日志 ID。

配置的下行 Stream ID 必须与设备发送时使用的编号一致。编号不一致时,即使连接成功,Demo 也收不到或采集不到对应数据。

采集对讲音频

对讲通常在播放页进行。采集期间,Demo 会同时记录当前的下行音视频和 Android 手机麦克风的上行音频。

  1. 打开「偏好设置」,确认「传输 Stream ID」与设备订阅的对讲 Stream ID 一致。
  2. 返回播放页,点击「抓数据」。
  3. 如果设备收到业务控制命令后才订阅对讲流,点击右上角的「命令」,发送双方约定的命令。设备建立连接后已经订阅时,跳过此步。
  4. 点击「启动麦克风」,并在首次使用时允许麦克风权限。
  5. 按实际操作复现问题。需要同时分析下行数据时,让设备继续发送对应的音频或视频。
  6. 点击「结束上传」,等待上传完成并复制日志 ID。

需要控制命令时,Demo 不会自动发送。命令 ID、内容格式和内容由设备业务协议定义,具体操作见收发命令消息

上传失败后重试

上传失败时,采集数据会保留在手机上。网络恢复后点击「重试上传」,无需重新复现。

提交排查信息

联系技术支持时,请提供:

  • 日志 ID。
  • 问题现象、最短复现步骤和大致发生时间。
  • 设备型号,以及本次使用的下行音视频 Stream ID、传输 Stream ID 和编码格式。

不要在工单、聊天记录或截图中提交连接 Token、SecretKeyIddevice_secret_key 等敏感信息。

TiRTC 开发文档