Skip to content

Go 客户端 API 说明 ​

使用 Go 客户端 SDK 连接设备、接收解码或编码音视频帧、保存文件并收发消息。尚未安装时先看 Go 客户端 SDK 接入。自行部署 RTC 节点请使用服务端 Go SDK。

Client 与 Connection ​

go
client, err := tirtc.NewClient(tirtc.ClientOptions{
    AppID: appID, AccessKeyID: accessKeyID, AccessKeySecret: accessKeySecret,
    CacheDir: cacheDir, // 可写绝对路径
})
connection, err := client.NewConnection(tirtc.ConnOptions{
    OnStateChanged: func(state tirtc.ConnState, err error) {},
})

上述为签名示意;实际代码逐步检查 err,见下方完整示例。使用一对 Access Key 托管鉴权,同一进程一个 RTC Client。Connect(ctx, deviceID) 等待初次连接结果;成功返回后才订阅或发送,后续断线通过 OnStateChanged 通知。

操作方法
连接 / 断开 / 状态Connect(ctx, deviceID) error、Disconnect() error、State() ConnState
音频订阅SubscribeAudio(id) error、UnsubscribeAudio(id) error
视频订阅与关键帧SubscribeVideo(id) error、UnsubscribeVideo(id) error、RequestVideoKeyframe(id) error
命令SendCommand(commandID uint32, data []byte) error;ConnOptions.OnCommand 接收
流消息SendStreamMessage(id uint8, timestamp time.Duration, data []byte) error;ConnOptions.OnStreamMessage 接收
关闭Close() error

消息接收函数在创建连接时传入:

go
OnCommand       func(commandID uint32, data []byte)
OnStreamMessage func(streamID uint8, timestamp time.Duration, data []byte)

Stream ID 为 0..15,设备端发送、Output 绑定和订阅必须使用同一编号;同方向音频和视频使用不同编号。示例 10 / 11 不是保留值。自定义命令字从 0x2001 起;流消息时间戳使用非负整毫秒,最大为 uint32 毫秒。

接收第一帧视频 ​

将以下代码放入 cmd/app/main.go。环境中设置 TIRTC_APP_ID、TIRTC_ACCESS_KEY_ID、TIRTC_SECRET_KEY_ID、TIRTC_DEVICE_ID,设备发送视频流 11。按接入指南使用 tirtc-build 构建后运行;收到帧会输出尺寸,30 秒无帧则报错。

go
package main

import (
    "context"
    "errors"
    "fmt"
    "log"
    "os"
    "path/filepath"
    "time"

    tirtc "github.com/tangeai/tirtc-client-go/v2"
)

// 在业务协程释放;回调正在退出时只对 ErrInUse 做有界重试。
func closeResource(closeFn func() error) error {
    for attempt := 0; attempt < 100; attempt++ {
        err := closeFn()
        if !errors.Is(err, tirtc.ErrInUse) { return err }
        time.Sleep(10 * time.Millisecond)
    }
    return tirtc.ErrInUse
}

func receive() (result error) {
    cache, err := filepath.Abs(".tirtc-cache")
    if err != nil { return err }
    client, err := tirtc.NewClient(tirtc.ClientOptions{
        AppID: os.Getenv("TIRTC_APP_ID"),
        AccessKeyID: os.Getenv("TIRTC_ACCESS_KEY_ID"),
        AccessKeySecret: os.Getenv("TIRTC_SECRET_KEY_ID"), CacheDir: cache,
    })
    if err != nil { return err }
    var conn *tirtc.Conn
    var video *tirtc.VideoOutput
    defer func() {
        // 子对象未成功关闭时保留父对象,不继续销毁。
        if video != nil {
            if err := closeResource(video.Close); err != nil {
                result = errors.Join(result, err); return
            }
        }
        if conn != nil {
            if err := closeResource(conn.Close); err != nil {
                result = errors.Join(result, err); return
            }
        }
        result = errors.Join(result, closeResource(client.Close))
    }()
    failures := make(chan error, 1)
    report := func(err error) {
        if err != nil { select { case failures <- err: default: } }
    }
    conn, err = client.NewConnection(tirtc.ConnOptions{
        OnStateChanged: func(state tirtc.ConnState, err error) { report(err) },
    })
    if err != nil { return err }
    first := make(chan tirtc.VideoFrame, 1)
    video, err = tirtc.NewVideoOutput(tirtc.VideoOutputOptions{
        OnFrame: func(frame tirtc.VideoFrame) {
            select { case first <- frame: default: }
        },
        OnError: report,
    })
    if err != nil { return err }
    if err = video.Attach(conn, 11); err != nil { return err }
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    if err = conn.Connect(ctx, os.Getenv("TIRTC_DEVICE_ID")); err != nil { return err }
    if err = conn.SubscribeVideo(11); err != nil { return err }
    if err = conn.RequestVideoKeyframe(11); err != nil { return err }
    select {
    case frame := <-first:
        fmt.Printf("video: %dx%d\n", frame.Width, frame.Height)
        return nil
    case err := <-failures:
        return err
    case <-time.After(30 * time.Second):
        return fmt.Errorf("30 秒内未收到视频帧")
    }
}

func main() {
    if err := receive(); err != nil { log.Fatal(err) }
}

这是运行后退出的首帧示例。常驻应用清理失败时,应保留对象引用,在业务控制流程处理错误或重试;不要直接开始下一轮 Client。

Output 与 Frame ​

需要的数据创建方法及回调参数
解码音频NewAudioOutput(AudioOutputOptions{OnFrame: func(AudioFrame)})
解码视频NewVideoOutput(VideoOutputOptions{OnFrame: func(VideoFrame)})
编码音频NewEncodedAudioOutput(EncodedAudioOutputOptions{OnFrame: func(EncodedAudioFrame)})
编码视频NewEncodedVideoOutput(EncodedVideoOutputOptions{OnFrame: func(EncodedVideoFrame)})

四类构造器均返回 (output, error);Output 独立创建,调用 Attach(conn, id) 绑定,Detach() 解绑,最终 Close()。可同时使用解码和编码 Output。回调应快速返回,业务队列保持有界。SDK 不创建播放窗口、不驱动扬声器,也不提供音视频输入。

AudioFrame.Data 和 VideoFrame.Planes[].Data 是 Go slice,可在回调后持有;视频按各 Plane 的 Stride 读取。结合帧的格式、尺寸、采样率、声道和时间戳解释数据,不要假设固定格式。

MP4 与 JPEG ​

  • conn.StartRecording(tirtc.StartRecordingOptions{VideoStreamID: 11, AudioStreamID: &audioID}) 返回任务与错误。可选音频 ID 必须与视频不同;先订阅并确认设备正在送流。
  • task.Stop() 返回 RecordingFile 与错误,文件有 Path、Duration。
  • 解码视频 Output 的 TakeSnapshot() 返回 SnapshotFile 与错误;需要已有可用视频帧。
  • 用标准 os / io 将临时文件复制到业务目录,成功后调用 file.Delete()。先显式停止录制并领取文件,再关闭连接。

释放与错误处理 ​

先停止录制,再关闭或解绑 Output,关闭 Connection,最后关闭 Client。仍有 Output 绑定或录制任务时,Conn.Close() 返回 ErrInUse;Client.Close() 会处理连接并解绑 Output,应用仍需最终关闭这些 Output。

不要在回调中关闭同一对象或其父对象;通知业务协程清理。Close() 遇到正在执行的回调可能返回 ErrInUse,使用有界重试,并保留失败时的对象引用。

通过 errors.Is(err, tirtc.ErrInUse) 判断错误类别,或用 errors.As 取得 *tirtc.Error 的 Code,再用 tirtc.ErrorName(code) 查错误名。排查时保留操作名、错误和 client.UploadLogs() 返回的日志 ID。

TiRTC 开发文档