Golang

Updated at:

Integrate the ApsaraVideo Real-time Communication (ARTC) SDK into a Linux Go project to build a real-time audio and video application for server-side scenarios such as video conferencing, interactive streaming, and cloud recording.

Key concepts

Before you begin, review the following key concepts:

  • ARTC SDK: Alibaba Cloud's SDK for real-time audio and video interactions.

  • GRTN: Alibaba Cloud's Global Realtime Transport Network, providing ultra-low latency, high-quality, secure, and reliable audio and video communication services.

  • channel: A virtual room for real-time audio and video interactions.

  • host: A role that allows a user to publish audio and video streams in a channel and subscribe to streams published by other hosts.

  • viewer: A role that enables a user to subscribe to audio and video streams in a channel but not publish streams.

image
  1. Call setChannelProfile to set the channel scenario, and then call joinChannel to join a channel:

    • In a video call scenario, all users have the host role and can publish and subscribe to streams.

    • In an interactive streaming scenario, you must call setClientRole to set the user role. Set the role to host for users who will publish a stream. If a user only needs to subscribe to a stream, set their role to viewer.

  2. After joining a channel, a user's role determines whether they can publish or subscribe to streams:

    • All users in a channel can subscribe to its audio and video streams.

    • A host can publish audio and video streams in the channel.

    • If a viewer needs to publish a stream, they must call the setClientRole method to switch their role to host.

Sample project

The SDK package includes a sample program:

Sample

File

Description

Feature demo

demo.go

Demonstrates full stream publishing and subscription, token generation, and more

To run the sample:

cd Go
go run demo.go

Prerequisites

  • Operating system: Linux (kernel 2.6 or later)

  • Go version: Go 1.16 or later

  • Network: A stable Internet connection

  • Application setup: Obtain the AppID and AppKey for your ApsaraVideo Real-time Communication application. For details, see Create Application

Implementation steps

Step 1: Import the SDK

The SDK package has the following directory structure:

AliRTCSDK_Linux/
└── Go/
    ├── alirtc/
    │   ├── AliRTCEngine.go             # Go interface wrapper
    │   ├── AliRTCEngineImpl.go         # Interface implementation
    │   ├── AliRTCLinuxSdkDefine.go     # Data structures and enumerations
    │   └── lib/
    │       ├── AliRtcCoreService       # Background service process (absolute path required)
    │       └── libAliRtcLinuxEngine.so # Full dynamic library of the SDK
    ├── demo.go                         # Feature demo sample
    └── go.mod

In your Go file, import the SDK:

import "alirtc"

Configure Go Modules (if module errors occur):

cd alirtc && go mod init alirtc && cd ..
go mod init your_project
go mod edit -replace alirtc=./alirtc
go mod tidy

Before running, set the dynamic library search path:

export LD_LIBRARY_PATH=/path/to/Go/alirtc/lib:$LD_LIBRARY_PATH

Step 2: Implement the event handler struct

Implement the alirtc.EngineEventHandlerInterface interface to receive notifications from the SDK.

package main

import (
    "fmt"
    "alirtc"
)

type VideoCallEventHandler struct{}

func (e *VideoCallEventHandler) OnJoinChannelResult(result int, channel, userId string) {
    if result == 0 {
        fmt.Printf("[OnJoinChannelResult] User %s joined channel %s successfully\n", userId, channel)
    } else {
        fmt.Printf("[OnJoinChannelResult] Failed to join, error: %d\n", result)
    }
}

func (e *VideoCallEventHandler) OnRemoteUserOnLineNotify(uid string) {
    fmt.Printf("[OnRemoteUserOnLineNotify] uid: %s\n", uid)
}

func (e *VideoCallEventHandler) OnRemoteUserOffLineNotify(uid string) {
    fmt.Printf("[OnRemoteUserOffLineNotify] uid: %s\n", uid)
}

func (e *VideoCallEventHandler) OnRemoteTrackAvailableNotify(uid string,
    audioTrack alirtc.AudioTrack, videoTrack alirtc.VideoTrack) {
    fmt.Printf("[OnRemoteTrackAvailableNotify] uid: %s, audio: %d, video: %d\n",
        uid, audioTrack, videoTrack)
}

// OnSubscribeMixAudioFrame: Receive mixed PCM audio frames from remote users
// Triggered when SubscribeAudioFormat = AudioFormatMixedPcm in the subscription configuration
func (e *VideoCallEventHandler) OnSubscribeMixAudioFrame(frame alirtc.AudioFrame) {

    // frame.Pcm.Data        PCM data ([ ]byte, int16_t format)

    // frame.Pcm.Channels    Number of sound channels
    // frame.Pcm.SampleRates Sample rate
    // Write to file, send to audio device, or decode and play here
}

// OnSubscribeAudioFrame: Receive unmixed PCM frames from individual remote users, identified by uid
// Triggered when SubscribeAudioFormat = AudioFormatPcmBeforMixing in the subscription configuration
func (e *VideoCallEventHandler) OnSubscribeAudioFrame(uid string, frame alirtc.AudioFrame) {
    // uid identifies which remote user sent this frame
    // Process audio data per user here
}

// OnRemoteVideoSample: Receive remote video frames
// uid identifies which remote user sent this video stream
func (e *VideoCallEventHandler) OnRemoteVideoSample(uid string, frame alirtc.VideoFrame) {
    // uid identifies which remote user sent this frame
    // frame.Frame.Width / frame.Frame.Height resolution
    // Write to file, send to renderer, or send to video decoder here
}

func (e *VideoCallEventHandler) OnError(errorCode alirtc.ErrorCode) {
    fmt.Printf("[OnError] error_code: 0x%X\n", errorCode)
}

Step 3: Authenticate with a token

The Go SDK provides the GenerateToken method to generate a single-parameter join token directly on the client.

Token generation flow:

  1. Concatenate the string: appId + appKey + channelId + userId + nonce + timestamp

  2. Compute the SHA-256 hash and obtain a hexadecimal string

  3. Build JSON: {"appid":..., "channelid":..., "userid":..., "nonce":..., "timestamp":..., "token":<sha256>}

  4. Base64-encoded

import "time"

authInfo := alirtc.AuthInfo{
    AppID:    "your_app_id",
    Channel:  "your_channel_id",
    UserID:   "your_user_id",
    UserName: "your_user_id",
}

expire := time.Now().Add(24 * time.Hour)
authInfo.Timestamp = int64(expire.Unix()) // Expires in 24 hours

appKey := "your_app_key" // Never expose AppKey in client code in production

// Call GenerateToken to generate a single-parameter join token (engine instance must be created first)
authInfo.Token = linuxEngine.GenerateToken(authInfo, appKey)
Note

Security note: Generating tokens on the client works only for development and testing. In production, your business server must generate and deliver tokens. Do not expose your AppKey in client code.

Step 4: Create and initialize the audio and video engine

Create an engine instance by calling alirtc.CreateAliRTCEngine and passing in your event handler object.

import (
    "encoding/json"
    "fmt"
    "os"
    "alirtc"
)

eventHandler := &VideoCallEventHandler{}

coreServicePath := "/path/to/Go/alirtc/lib/AliRtcCoreService" // Absolute path to AliRtcCoreService
h5mode := false // Set to true only for interoperability with Web (H5)

extraJobj := map[string]interface{}{
    "user_specified_disable_audio_ranking": "true",
}
extraBytes, _ := json.Marshal(extraJobj)
extra := string(extraBytes)

linuxEngine := alirtc.CreateAliRTCEngine(
    eventHandler,
    42000, 45000,    // IPC port range for communication with AliRtcCoreService process
    "/tmp",          // Log file directory
    coreServicePath,
    h5mode,
    extra,
)

if linuxEngine == nil {
    fmt.Fprintln(os.Stderr, "Failed to create RTC engine")
    os.Exit(1)
}
Note

Each engine instance starts one AliRtcCoreService background process (one virtual user).

Step 5: Configure audio and video properties

Set the user role with SetClientRole and configure video encoding parameters with SetVideoEncoderConfiguration.

// Call SetClientRole to set the role to interactive (streamer), allowing both publishing and subscribing
linuxEngine.SetClientRole(alirtc.AliEngineClientRoleInteractive)

// Call SetVideoEncoderConfiguration to set video encoding parameters
videoConfigPtr := alirtc.NewAliEngineVideoEncoderConfiguration(
    720, 1280,
    alirtc.AliEngineFrameRateFps15,
    1200, 0,
    alirtc.AliEngineVideoEncoderOrientationModeAdaptive,
    alirtc.AliEngineVideoMirrorModeDisabled,
    alirtc.AliEngineRotationMode_0,
)
videoConfig := *videoConfigPtr
linuxEngine.SetVideoEncoderConfiguration(videoConfig)

Step 6: Configure stream publishing and subscription

Configure how audio and video streams are published and subscribed. Enable the Linux-specific external audio and video sources mode.

// Call PublishLocalVideoStream / PublishLocalAudioStream to enable local stream publishing
linuxEngine.PublishLocalVideoStream(true)
linuxEngine.PublishLocalAudioStream(true)

// Linux has no built-in camera or microphone. Call SetExternalVideoSource to enable external video input.
// Push YUV frame data using PushExternalVideoFrame.
linuxEngine.SetExternalVideoSource(true, alirtc.VideoSourceCamera, alirtc.RenderModeFill)

// Call SetExternalAudioSource to enable external audio input. Push PCM frame data using PushExternalAudioFrameRawData.
linuxEngine.SetExternalAudioSource(true, 16000 /* sample rate */, 1 /* number of sound channels */)

Configure subscription behavior at join time (set in JoinChannelConfig in Step 7):

joinConfig := alirtc.NewJoinChannelConfig()
joinConfig.ChannelProfile    = alirtc.ChannelProfileInteractiveLive
joinConfig.PublishMode       = alirtc.PublishAutomatically     // Publish automatically
joinConfig.SubscribeMode     = alirtc.SubscribeAutomatically   // Subscribe automatically
joinConfig.PublishAvsyncMode = alirtc.PublishAvsyncWithPts

// Two options for audio subscription format:
// AudioFormatMixedPcm: Receive mixed PCM for the entire channel. Triggers OnSubscribeMixAudioFrame.
// AudioFormatPcmBeforMixing: Receive unmixed PCM per remote user. Triggers OnSubscribeAudioFrame (with uid).
joinConfig.SubscribeAudioFormat = alirtc.AudioFormatMixedPcm

// Receive H264 video frames. Triggers OnRemoteVideoSample.
joinConfig.SubscribeVideoFormat = alirtc.VideoFormatH264

Step 7: Join the channel

Call JoinChannel with the token and channel config from Step 3.

// Call JoinChannel to join the channel (single-parameter join)
linuxEngine.JoinChannel(authInfo.Token, authInfo.Channel, authInfo.UserID, authInfo.UserName, joinConfig)
Note

Do not call JoinChannel more than once. Use GenerateToken only for development and testing. In production, obtain tokens from your business server to prevent AppKey leaks.

Step 8: Push external video frames

Linux servers have no built-in camera driver. Feed YUV video data into the SDK through external input. The following example reads I420-format YUV frames from a file in a loop. In production, replace this with camera driver output or video decoder output.

import (
    "os"
    "time"
    "alirtc"
)

go func() {
    width, height, fps := 720, 1280, 15
    frameSize := width * height * 3 / 2 // I420

    videoFile, err := os.Open("/tmp/test_720p.yuv")
    if err != nil {
        panic(err)
    }
    defer videoFile.Close()

    vTs := 0
    for running {

        data := make([]byte, frameSize)

        n, err := videoFile.Read(data)
        if err != nil || n < frameSize {
            videoFile.Seek(0, 0) // Rewind to start when end of file reached
            continue
        }

        videoSample := alirtc.VideoDataSample{
            Width:      width,
            Height:     height,
            Format:     alirtc.VideoDataFormatI420,
            BufferType: alirtc.VideoBufferTypeRawData,
            Rotation:   0,
        }
        videoSample.StrideY   = width
        videoSample.StrideU   = width / 2
        videoSample.StrideV   = width / 2
        videoSample.DataLen   = frameSize
        videoSample.Data      = data
        videoSample.TimeStamp = vTs

        linuxEngine.PushExternalVideoFrame(&videoSample, alirtc.VideoSourceCamera)
        vTs += 1000 / fps
        time.Sleep(time.Duration(1000/fps) * time.Millisecond)
    }
}()

Step 9: Push external audio frames

Linux servers have no built-in microphone recording interface. Feed PCM audio data into the SDK through external input. The following example reads PCM frames (int16_t, 16 kHz, mono) from a file in a loop. In production, replace this with microphone driver output or audio decoder output.

go func() {
    sampleRate := 16000
    channels   := 1
    frameMs    := 20 // 20 ms per frame
    frameSize  := (sampleRate / 1000) * frameMs * 2 * channels // int16_t = 2 bytes

    audioFile, err := os.Open("/tmp/test_16k_mono.pcm")
    if err != nil {
        panic(err)
    }
    defer audioFile.Close()

    aTs := 0
    for running {

        data := make([]byte, frameSize)

        n, err := audioFile.Read(data)
        if err != nil || n < frameSize {
            audioFile.Seek(0, 0) // Rewind to start when end of file reached
            continue
        }

        ret := linuxEngine.PushExternalAudioFrameRawData(data, frameSize, int64(aTs))
        if ret != 0 {
            // SDK buffer is full. Rewind file pointer and retry later.
            audioFile.Seek(int64(-n), 1)
            time.Sleep(20 * time.Millisecond)
            continue
        }

        aTs += frameMs
        time.Sleep(time.Duration(frameMs) * time.Millisecond)
    }
}()

Step 10: Handle remote audio and video playback

Linux servers have no built-in audio or video playback devices. The SDK delivers remote media frames through callbacks. Your application handles them by writing to files, sending to decoders, or connecting to playback devices.

Audio playback

Based on the SubscribeAudioFormat setting in Step 6, one of these callbacks triggers when remote audio frames arrive:

// AudioFormatMixedPcm mode: Receive mixed PCM for the entire channel
func (e *VideoCallEventHandler) OnSubscribeMixAudioFrame(frame alirtc.AudioFrame) {

    // frame.Pcm.Data        PCM data ([ ]byte, int16_t format)

    // frame.Pcm.Channels    Number of sound channels
    // frame.Pcm.SampleRates Sample rate
    // Write to file, send to audio device, or decode and play here
}

// AudioFormatPcmBeforMixing mode: Receive unmixed PCM per remote user
func (e *VideoCallEventHandler) OnSubscribeAudioFrame(uid string, frame alirtc.AudioFrame) {
    // uid identifies which remote user sent this frame
    // Process audio data per user here
}

Video playback

When remote video frames arrive, the OnRemoteVideoSample callback triggers. The frame format depends on the SubscribeVideoFormat setting in Step 6:

func (e *VideoCallEventHandler) OnRemoteVideoSample(uid string, frame alirtc.VideoFrame) {
    // uid identifies which remote user sent this frame
    // frame.Frame.Width / frame.Frame.Height resolution
    // Write to file, send to renderer, or send to video decoder here
}

Step 11: Leave the channel and destroy the engine

When finished, release resources properly: stop publishing, leave the channel, and then destroy the engine.

// Stop the external video push goroutine
running = false
// Wait for goroutine to exit (use sync.WaitGroup if needed)

// Call PublishLocalVideoStream(false) / PublishLocalAudioStream(false) to stop publishing
linuxEngine.PublishLocalVideoStream(false)
linuxEngine.PublishLocalAudioStream(false)

// Call LeaveChannel to leave the channel
linuxEngine.LeaveChannel()

// Wait for OnLeaveChannelResult callback (stopSignal set to true) before destroying the engine
for !stopSignal {
    time.Sleep(time.Second)
}

// Call Release to destroy the engine (must be called after LeaveChannel)
linuxEngine.Release()
linuxEngine = nil

FAQ

Q: How do I fix a module error when I run go run demo.go?

Delete the go.mod files in the current directory and the alirtc directory. Then run:

cd alirtc && go mod init alirtc && cd ..
go mod init your_project
go mod edit -replace alirtc=./alirtc
go mod tidy
go run demo.go

Q: When should I set h5mode to true?

Set h5mode to true only when you need interoperability with Web (H5) pages. For Linux-to-Linux interoperability, use false.

Q: How do I handle expired tokens?

Listen for the OnAuthInfoWillExpire callback (token about to expire). Regenerate the token and call the engine’s refresh method to update credentials. You do not need to rejoin the channel.

Listen for the OnAuthInfoExpired callback (token expired). Leave the channel and rejoin with a new token.

Q: Why does the runtime report “cannot open shared object file”?

error while loading shared libraries: libAliRtcLinuxEngine.so: cannot open shared object file

Solution: Run export LD_LIBRARY_PATH=/path/to/Go/alirtc/lib:$LD_LIBRARY_PATH.