Python

Updated at:

This topic describes how to integrate the ARTC SDK into a Linux Python project, enabling you to quickly build a simple real-time audio and video interaction program. This applies to server-side scenarios such as video conferencing, interactive streaming, and cloud recording.

Feature description

Before you begin, understand the following key concepts:

  • ARTC SDK: An SDK provided by Alibaba Cloud that helps developers quickly implement real-time audio and video interaction.

  • Global Realtime Transport Network (GRTN): A globally distributed network engineered for real-time media, ensuring ultra-low latency, high-quality, and secure communication.

  • Channel: A virtual room that users join to communicate with each other. All users in the same channel can interact in real time.

  • Host: A user who can publish audio and video streams in a channel and subscribe to streams published by other hosts.

  • Viewer: A user who can subscribe to audio and video streams in a channel but cannot publish their own.

Basic process for implementing real-time audio and video interaction:

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

    • Video call scenario: All users are hosts and can both publish and subscribe to streams.

    • Interactive streaming scenario: Roles must be set using setClientRole before joining a channel. For users who will publish streams, set the role to host. If a user only needs to subscribe to streams, set the role to viewer.

  2. After joining the channel, users have different publishing and subscribing behaviors based on their roles:

    • All users can receive audio and video streams within that channel.

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

    • If a viewer wants to publish streams, call the setClientRole method to switch the role to host.

Example Project

The SDK package provides an example program:

Example

File

Description

Feature demo

demo.py

Includes a complete demo of stream ingest and stream pulling, Token generation, and other features.

Run the example:

cd Python
python3 demo.py

Prerequisites

  • Operating system: Linux (kernel 2.6+).

  • Python version: Python 3.6+.

  • Network environment: A stable Internet connection.

  • Application preparation: Obtain the AppID and AppKey for your real-time audio and video application. For more information, see Create Application.

Implementation Steps

Step 1: Import SDK

The SDK package directory structure is as follows:

AliRTCSDK_Linux/
└── Python/
    ├── Release/
    │   └── lib/
    │       ├── AliRtcCoreService             # Background service process (specify absolute path)
    │       ├── libAliRtcLinuxEngine.so        # Complete SDK dynamic library
    │       └── libonnxruntime.so.1.16.3       # AI denoising dependency library
    ├── AliRTCEngine.py                        # Python interface encapsulation
    ├── AliRTCEngineImpl.py                    # Interface implementation
    ├── AliRTCLinuxSdkDefine.py                # Data structure and enumeration definitions
    └── demo.py                                # Feature demo example

Import the SDK into your Python file:

from AliRTCLinuxSdkDefine import *
import AliRTCEngine

Set the dynamic library search path before running.

export LD_LIBRARY_PATH=/path/to/Python/Release/lib:$LD_LIBRARY_PATH

Step 2: Implement Event Callback Class

Inherit AliRTCEngine.EngineEventHandlerInterface to implement event callbacks. This implementation receives various notifications pushed by the SDK.

import AliRTCEngine
from AliRTCLinuxSdkDefine import ERROR_CODE

class VideoCallEventHandler(AliRTCEngine.EngineEventHandlerInterface):

    def OnJoinChannelResult(self, result: int, channel: str, userId: str) -> None:
        if result == 0:
            print(f"[OnJoinChannelResult] User {userId} joined channel {channel} successfully")
        else:
            print(f"[OnJoinChannelResult] Failed to join, error: {result}")

    def OnRemoteUserOnLineNotify(self, uid: str) -> None:
        print(f"[OnRemoteUserOnLineNotify] uid: {uid}")

    def OnRemoteUserOffLineNotify(self, uid: str) -> None:
        print(f"[OnRemoteUserOffLineNotify] uid: {uid}")

    # OnSubscribeMixAudioFrame: Receives mixed remote PCM audio frames
    # Triggered when subscribeAudioFormat = AudioFormatMixedPcm in the subscription configuration
    def OnSubscribeMixAudioFrame(self, frame: AliRTCEngine.AudioFrame) -> None:
        # frame.data       PCM data (bytes, int16_t format)
        # frame.channels   The number of sound channels
        # frame.sampleRate Sample rate
        # Write to a file, send to an audio device, or decode and play here
        pass

    # OnSubscribeAudioFrame: Receives unmixed PCM frames from each remote user. The uid distinguishes audio streams from different remote users.
    # Triggered when subscribeAudioFormat = AudioFormatPcmBeforMixing in the subscription configuration
    def OnSubscribeAudioFrame(self, uid: str, frame: AliRTCEngine.AudioFrame) -> None:
        # The uid identifies which remote user the frame comes from
        # Process audio data separately by user here
        pass

    # OnRemoteVideoSample: Receives remote video frames
    # The uid distinguishes video streams from different remote users
    def OnRemoteVideoSample(self, uid: str, frame: AliRTCEngine.VideoFrame) -> None:
        # The uid identifies which remote user the frame comes from
        # Write to a file, send to a renderer, or video decoder here
        pass

    def OnError(self, error_code: ERROR_CODE) -> None:
        print(f"[OnError] error_code: {error_code.value:#010x}")

Step 3: Authenticate Token

The Python SDK has a built-in GenerateToken method. You can use it to directly generate a channel-joining Token on the client.

Token generation flow:

  1. Concatenate strings: appId + appKey + channelId + userId + nonce + timestamp

  2. Perform SHA-256 hashing to obtain a hexadecimal string.

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

  4. Base64 encode.

import datetime
import time
from AliRTCLinuxSdkDefine import AuthInfo

authInfo = AuthInfo()
authInfo.appid    = "your_app_id"
authInfo.channel  = "your_channel_id"
authInfo.userid   = "your_user_id"
authInfo.username = "your_user_id"

expire = datetime.datetime.now() + datetime.timedelta(days=1)
authInfo.timestamp = int(time.mktime(expire.timetuple()))  # Expires after 24 hours

app_key = "your_app_key"  # Do not expose AppKey in client code in a production environment

# Call GenerateToken to generate a single-parameter channel-joining Token (create an engine instance first)
authInfo.token = linuxEngine.GenerateToken(authInfo, app_key)
Note

Token security tip: The example generates the Token locally on the client. This applies only to the development and testing phases. In a production environment, your business server must generate and distribute the Token. Avoid exposing the AppKey in client code.

Step 4: Create and Initialize Audio and Video Engine

Call AliRTCEngine.CreateAliRTCEngine to create an engine instance. Pass the event callback object.

import json
import os
import AliRTCEngine

event_handler = VideoCallEventHandler()

current_path = os.getcwd()
core_service_path = os.path.abspath(
    os.path.join(current_path, "Release", "lib", "AliRtcCoreService")
)

extra_jobj = {"user_specified_disable_audio_ranking": "true"}
extra = json.dumps(extra_jobj)

h5mode = False  # Set to True for interoperability with the Web client

linuxEngine = AliRTCEngine.CreateAliRTCEngine(
    event_handler,
    42000, 45000,       # IPC port range for communication with the AliRtcCoreService process
    "/tmp",             # Log file directory
    core_service_path,  # AliRtcCoreService absolute path
    h5mode,
    extra
)

if linuxEngine is None:
    print("Failed to create RTC engine")
    exit(1)
Note

Each time you create an engine instance, the SDK starts a corresponding AliRtcCoreService background process (corresponding to a virtual user).

Step 5: Set Audio and Video Properties

Call SetClientRole to set the user role. Call SetVideoEncoderConfiguration to configure video encoding parameters.

from AliRTCLinuxSdkDefine import (
    AliEngineClientRole, AliEngineVideoEncoderConfiguration,
    AliEngineFrameRate, AliEngineVideoEncoderOrientationMode,
    AliEngineVideoMirrorMode, AliEngineRotationMode
)

# Call SetClientRole to set the user role to interactive mode (streamer). This allows both publishing and subscribing.
linuxEngine.SetClientRole(AliEngineClientRole.AliEngineClientRoleInteractive)

# Call SetVideoEncoderConfiguration to set video encoding parameters
video_config = AliEngineVideoEncoderConfiguration(
    width=720, height=1280,
    f=AliEngineFrameRate.AliEngineFrameRateFps15,
    b=1200,
    ori=AliEngineVideoEncoderOrientationMode.AliEngineVideoEncoderOrientationModeAdaptive,
    mr=AliEngineVideoMirrorMode.AliEngineVideoMirrorModeDisabled,
    rotation=AliEngineRotationMode.AliEngineRotationMode_0
)
linuxEngine.SetVideoEncoderConfiguration(video_config)

Step 6: Set Stream Ingest and Stream Pulling Properties

Configure audio and video publishing and subscribing behaviors. Enable the external audio and video source mode, which is unique to the Linux platform.

from AliRTCLinuxSdkDefine import (
    VideoSource, RenderMode, JoinChannelConfig, ChannelProfile,
    PublishMode, SubscribeMode, PublishAvsyncMode, AudioFormat, VideoFormat
)

# Call PublishLocalVideoStream / PublishLocalAudioStream to enable local audio and video publishing
linuxEngine.PublishLocalVideoStream(True)
linuxEngine.PublishLocalAudioStream(True)

# Linux has no built-in camera/microphone. Call SetExternalVideoSource to enable an external video source.
# Input YUV frame data using PushExternalVideoFrame.
linuxEngine.SetExternalVideoSource(True,
    sourceType=VideoSource.VideoSourceCamera,
    renderMode=RenderMode.RenderModeFill)

# Call SetExternalAudioSource to enable an external audio source. Input PCM frame data using PushExternalAudioFrameRawData.
linuxEngine.SetExternalAudioSource(True, sampleRate=16000, channelsPerFrame=1)

Configure the subscription mode when joining a channel (set in JoinChannelConfig in Step 7):

join_config = JoinChannelConfig()
join_config.channelProfile    = ChannelProfile.ChannelProfileInteractiveLive
join_config.publishMode       = PublishMode.PublishAutomatically      # Automatically ingest streams
join_config.subscribeMode     = SubscribeMode.SubscribeAutomatically  # Automatically subscribe
join_config.publishAvsyncMode = PublishAvsyncMode.PublishAvsyncWithPts

# There are two options for audio subscription format:
# AudioFormatMixedPcm: Receives mixed PCM for the entire channel from all remote users, triggering OnSubscribeMixAudioFrame
# AudioFormatPcmBeforMixing: Receives PCM for each remote user separately, triggering OnSubscribeAudioFrame (includes uid)
join_config.subscribeAudioFormat = AudioFormat.AudioFormatMixedPcm

# Receives H264 video frames, triggering OnRemoteVideoSample
join_config.subscribeVideoFormat = VideoFormat.VideoFormatH264

Step 7: Join Channel

Call JoinChannel. Pass the Token generated in Step 3 and the channel configuration to join the channel.

# Call JoinChannel to join a channel (single-parameter channel joining)
linuxEngine.JoinChannel(
    authInfo.token,
    authInfo.channel,
    authInfo.userid,
    authInfo.username,
    join_config
)
Note

Do not call JoinChannel repeatedly. The GenerateToken interface is for development and testing only. In a production environment, obtain the Token from your business server to prevent AppKey leakage.

Step 8: Push External Video Frames

The Linux platform has no built-in camera driver interface. You can input YUV video data into the SDK using external input. The following example reads and pushes frame data in a loop from an I420 format YUV file. In a production environment, replace this with camera driver or video decoder output.

import threading
import time
from AliRTCLinuxSdkDefine import VideoDataSample, VideoDataFormat, VideoBufferType, VideoSource

def push_video():
    width, height, fps = 720, 1280, 15
    frame_size = width * height * 3 // 2  # I420

    with open("/tmp/test_720p.yuv", "rb") as video_file:
        v_ts = 0
        while running:
            data = video_file.read(frame_size)
            if not data or len(data) < frame_size:
                video_file.seek(0)  # Loop and re-read after the file is finished
                continue

            sample = VideoDataSample()
            sample.width      = width
            sample.height     = height
            sample.strideY    = width
            sample.strideU    = width // 2
            sample.strideV    = width // 2
            sample.dataLen    = frame_size
            sample.format     = VideoDataFormat.VideoDataFormatI420
            sample.bufferType = VideoBufferType.VideoBufferTypeRawData
            sample.rotation   = 0
            sample.data       = data
            sample.timeStamp  = v_ts

            linuxEngine.PushExternalVideoFrame(sample, VideoSource.VideoSourceCamera)
            v_ts += 1000 // fps
            time.sleep(1 / fps)

video_thread = threading.Thread(target=push_video)
video_thread.start()

Step 9: Push External Audio Frames

The Linux platform has no built-in microphone recording interface. You can input PCM audio data into the SDK using external input. The following example reads and pushes frame data in a loop from a PCM file (int16_t, 16 kHz, mono channel). In a production environment, replace this with microphone driver or audio decoder output.

def push_audio():
    sample_rate = 16000
    channels    = 1
    frame_ms    = 20  # 20 ms per frame
    frame_size  = (sample_rate // 1000) * frame_ms * 2 * channels  # int16_t = 2 bytes
    a_ts = 0

    with open("/tmp/test_16k_mono.pcm", "rb") as audio_file:
        while running:
            data = audio_file.read(frame_size)
            if not data or len(data) < frame_size:
                audio_file.seek(0)  # Loop and re-read after the file is finished
                continue

            ret = linuxEngine.PushExternalAudioFrameRawData(data, frame_size, a_ts)
            if ret != 0:
                # SDK buffer is full. Roll back the file pointer and retry later.
                audio_file.seek(audio_file.tell() - frame_size)
                time.sleep(0.02)
                continue

            a_ts += frame_ms
            time.sleep(frame_ms / 1000)

audio_thread = threading.Thread(target=push_audio)
audio_thread.start()

Step 10: Handle Remote Audio and Video Playback

The Linux platform has no built-in audio and video playback device. Remote audio and video data is handed over to the application layer for self-processing via callback frames. For example, you can write to a file, send to a decoder, or connect to a playback device.

Audio Playback

Based on the subscribeAudioFormat configuration in Step 6, one of the following callbacks is triggered when remote audio frames are received:

# AudioFormatMixedPcm mode: Receives mixed PCM data for the entire channel from all remote users
def OnSubscribeMixAudioFrame(self, frame: AliRTCEngine.AudioFrame) -> None:
    # frame.data       PCM data (bytes, int16_t format)
    # frame.channels   The number of sound channels
    # frame.sampleRate Sample rate
    # Write to a file, send to an audio device, or decode and play here
    pass

# AudioFormatPcmBeforMixing mode: Receives unmixed PCM data for each user separately
def OnSubscribeAudioFrame(self, uid: str, frame: AliRTCEngine.AudioFrame) -> None:
    # The uid identifies which remote user the frame comes from
    # Process audio data separately by user here
    pass

Video Playback

When remote video frames are received, the OnRemoteVideoSample callback is triggered. The frame format is determined by subscribeVideoFormat in Step 6:

def OnRemoteVideoSample(self, uid: str, frame: AliRTCEngine.VideoFrame) -> None:
    # The uid identifies which remote user the frame comes from
    # Write to a file, send to a renderer, or video decoder here
    pass

Step 11: Leave Channel and Destroy Engine

You must release resources correctly. Stop stream ingest, leave the channel, and destroy the engine in sequence.

# Stop the external stream ingest thread
running = False
video_thread.join()
audio_thread.join()

# Call PublishLocalVideoStream(False) / PublishLocalAudioStream(False) to stop publishing
linuxEngine.PublishLocalVideoStream(False)
linuxEngine.PublishLocalAudioStream(False)

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

# Wait for the OnLeaveChannelResult callback (stop_signal set to True) before destroying the engine
while not stop_signal:
    time.sleep(1)

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

FAQ

Q: When do I set h5mode to True?

Enable it only when interoperating with the Web client (H5 page). For pure Linux client interoperability, set it to False.

Q: How do I handle an expired Token?

Listen for the OnAuthInfoWillExpire callback (Token is about to expire). Regenerate the Token and call the engine refresh interface to update the credentials. Rejoining the channel is not necessary.

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

Q: The runtime indicates that the dynamic library cannot be found.

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

Solution: Run export LD_LIBRARY_PATH=/path/to/Python/Release/lib:$LD_LIBRARY_PATH.