Python
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:
Call
setChannelProfileto set the scenario, and calljoinChannelto 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
setClientRolebefore 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.
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
setClientRolemethod to switch the role to host.
Example Project
The SDK package provides an example program:
Example | File | Description |
Feature demo |
| Includes a complete demo of stream ingest and stream pulling, Token generation, and other features. |
Run the example:
cd Python
python3 demo.pyPrerequisites
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 AliRTCEngineSet the dynamic library search path before running.
export LD_LIBRARY_PATH=/path/to/Python/Release/lib:$LD_LIBRARY_PATHStep 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:
Concatenate strings:
appId + appKey + channelId + userId + nonce + timestampPerform SHA-256 hashing to obtain a hexadecimal string.
Assemble JSON:
{"appid":..., "channelid":..., "userid":..., "nonce":..., "timestamp":..., "token":<sha256>}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)
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)
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
)
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.