Integrate the ARTC SDK for Linux (Java)
Integrate the ARTC SDK into a Linux Java project to build a real-time audio and video application for 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.
Sample project
The SDK includes a sample program:
|
Example |
File |
Description |
|
Feature demo |
|
Demos stream ingest, stream pulling, and token generation. |
Run the sample:
cd Demo
sh run.sh
Enter exit to stop the sample.
Prerequisites
-
Operating system: Linux (kernel 2.6+).
-
Java runtime: JDK 8+.
-
Network: Stable internet connection.
-
Application: An ARTC AppID and AppKey. Create an application.
Implementation steps
Step 1: Import the SDK
The SDK package has the following directory structure:
AliRTCSDK_Linux/
└── Java/
├── libs/
│ ├── AliRtcCoreService # Background service process (must specify an absolute path)
│ ├── alirtc_linux_java_multiprocess.jar # JAR bridge between the Java layer and the C++ engine
│ ├── gson-2.11.0.jar # JSON serialization dependency
│ └── libAliRtcLinuxEngine.so # Complete dynamic library for the SDK
└── Demo/
├── MainTest.java # Feature demo sample
└── run.sh # Script to compile and run
Compile and run the sample:
cd Demo
javac -g -cp ../libs/alirtc_linux_java_multiprocess.jar:../libs/gson-2.11.0.jar \
-encoding utf-8 MainTest.java
java -cp .:../libs/alirtc_linux_java_multiprocess.jar:../libs/gson-2.11.0.jar MainTest
Set the library path before running:
export LD_LIBRARY_PATH=/path/to/Java/libs:$LD_LIBRARY_PATH
Step 2: Implement the event callback class
Implement the AliRTCLinuxEngineListener interface to handle SDK event notifications.
import com.alivc.rtc.multiprocess.AliRTCLinuxEngine;
import com.alivc.rtc.multiprocess.AliRTCLinuxEngineListener;
class VideoCallEventHandler implements AliRTCLinuxEngineListener {
@Override
public void onJoinChannelResult(int result, String channel, String userId) {
if (result == 0) {
System.out.printf("[onJoinChannelResult] User %s joined channel %s successfully%n",
userId, channel);
} else {
System.out.printf("[onJoinChannelResult] Failed to join, error: %d%n", result);
}
}
@Override
public void onRemoteUserOnLineNotify(String uid) {
System.out.printf("[onRemoteUserOnLineNotify] uid: %s%n", uid);
}
@Override
public void onRemoteUserOffLineNotify(String uid) {
System.out.printf("[onRemoteUserOffLineNotify] uid: %s%n", uid);
}
@Override
public void onRemoteTrackAvailableNotify(String uid,
AliRTCLinuxEngine.AudioTrack audioTrack,
AliRTCLinuxEngine.VideoTrack videoTrack) {
System.out.printf("[onRemoteTrackAvailableNotify] uid: %s, audio: %s, video: %s%n",
uid, audioTrack, videoTrack);
}
// onSubscribeMixedAudioFrame: Receives remote PCM audio frames after mixing.
// Triggered when subscribeAudioFormat is set to AudioFormatMixedPcm in the subscription configuration.
@Override
public void onSubscribeMixedAudioFrame(AliRTCLinuxEngine.AudioFrame frame) {
if (frame != null && frame.type == AliRTCLinuxEngine.AudioFrameType.AudioFrameRawPcm) {
AliRTCLinuxEngine.AudioPcmFrame pcmFrame = frame.pcm;
// pcmFrame.data PCM data (byte[], int16_t format)
// pcmFrame.channels The number of sound channels
// pcmFrame.sampleRates Sample rate
// Write to a file, send to an audio device, or decode for playback here.
}
}
// onSubscribeAudioFrame: Receives unmixed PCM frames from each remote user. The uid distinguishes the audio streams of different remote users.
// Triggered when subscribeAudioFormat is set to AudioFormatPcmBeforeMixing in the subscription configuration.
@Override
public void onSubscribeAudioFrame(String uid, AliRTCLinuxEngine.AudioFrame frame) {
if (frame != null && frame.type == AliRTCLinuxEngine.AudioFrameType.AudioFrameRawPcm) {
AliRTCLinuxEngine.AudioPcmFrame pcmFrame = frame.pcm;
// The uid identifies which remote user the frame is from.
// Process audio data for each user separately here.
}
}
// onRemoteVideoSample: Receives remote video frames.
// The uid distinguishes the video streams of different remote users.
@Override
public void onRemoteVideoSample(String uid, AliRTCLinuxEngine.VideoFrame frame) {
if (frame != null && frame.type == AliRTCLinuxEngine.VideoFrameType.VideoFrameH264) {
AliRTCLinuxEngine.VideoH264Frame h264Frame = frame.h264;
// The uid identifies which remote user the frame is from.
// Write to a file, send to a renderer, or send to a video decoder here.
}
}
@Override
public void onError(int errorCode) {
System.out.printf("[onError] error_code: 0x%X%n", errorCode);
}
}
Step 3: Authentication token
The Java SDK includes a built-in generateToken method to generate a token for joining a channel.
Token generation process:
-
Concatenate the string:
appId + appKey + channelId + userId + nonce + timestamp -
Use SHA-256 hashing to generate a hexadecimal string.
-
Assemble the JSON:
{"appid":..., "channelid":..., "userid":..., "nonce":..., "timestamp":..., "token":<sha256>} -
Encode with Base64.
import java.time.Instant;
AliRTCLinuxEngine.AuthInfo authInfo = new AliRTCLinuxEngine.AuthInfo();
authInfo.appid = "your_app_id";
authInfo.channel = "your_channel_id";
authInfo.userid = "your_user_id";
authInfo.username = "your_user_id";
authInfo.nonce = null;
authInfo.timestamp = Instant.now().getEpochSecond() + 24 * 60 * 60; // Expires after 24 hours
String appKey = "your_app_key"; // In a production environment, do not expose the AppKey in the client code.
// Call generateToken to generate a single-parameter token for joining a channel (an engine instance must be created first).
authInfo.token = engineIns.generateToken(authInfo, appKey);
The sample generates tokens locally for development and testing only. In production, generate tokens on your server to avoid exposing the AppKey.
Step 4: Create and initialize the audio and video engine
Call AliRTCLinuxEngine.createInstance to create an engine instance with the event callback.
VideoCallEventHandler listener = new VideoCallEventHandler();
String coreServicePath = "/path/to/Java/libs/AliRtcCoreService"; // Specify the absolute path of AliRtcCoreService
boolean h5mode = false; // Set to true to interoperate with web clients
String extra = "{\"user_specified_disable_audio_ranking\":\"true\"}";
AliRTCLinuxEngine engineIns = AliRTCLinuxEngine.createInstance(
listener,
42000, 45000, // IPC port range for communication between the Java layer and the AliRtcCoreService process
"/tmp", // Log file directory
coreServicePath,
h5mode,
extra
);
if (engineIns == null) {
System.err.println("Failed to create RTC engine");
return;
}
The Java SDK uses TCP for IPC. Each engine instance starts a corresponding AliRtcCoreService background process that represents a virtual user.
Step 5: Set audio and video properties
Call setClientRole to set the user role and setVideoEncoderConfiguration to configure video encoding.
// Call setClientRole to set the user role to interactive mode (streamer) to publish and subscribe at the same time.
engineIns.setClientRole(AliRTCLinuxEngine.AliEngineClientRole.AliEngineClientRoleInteractive);
// Call setVideoEncoderConfiguration to set video encoding parameters.
AliRTCLinuxEngine.AliEngineVideoEncoderConfiguration videoConfig =
new AliRTCLinuxEngine.AliEngineVideoEncoderConfiguration();
videoConfig.dimensions = new AliRTCLinuxEngine.AliEngineVideoDimensions(720, 1280);
videoConfig.frameRate = AliRTCLinuxEngine.AliEngineFrameRate.AliEngineFrameRateFps15;
videoConfig.bitrate = 1200;
engineIns.setVideoEncoderConfiguration(videoConfig);
Step 6: Configure publish and subscribe settings
Configure publish and subscribe behavior. On Linux, enable external audio and video source mode.
// Call publishLocalVideoStream / publishLocalAudioStream to enable local audio and video publishing.
engineIns.publishLocalVideoStream(true);
engineIns.publishLocalAudioStream(true);
// Linux has no built-in camera or microphone. Call setExternalVideoSource to enable an external video source.
// Input YUV frame data using pushExternalVideoFrame.
engineIns.setExternalVideoSource(true,
AliRTCLinuxEngine.VideoSource.VideoSourceCamera,
AliRTCLinuxEngine.RenderMode.RenderModeFill);
// Call setExternalAudioSource to enable an external audio source. Input PCM frame data using pushExternalAudioFrameRawData.
engineIns.setExternalAudioSource(true, 16000 /* Sample rate */, 1 /* The number of sound channels */);
Set the subscription mode in JoinChannelConfig before joining:
AliRTCLinuxEngine.JoinChannelConfig joinConfig = new AliRTCLinuxEngine.JoinChannelConfig();
joinConfig.channelProfile = AliRTCLinuxEngine.ChannelProfile.ChannelProfileInteractiveLive;
joinConfig.publishMode = AliRTCLinuxEngine.PublishMode.PublishAutomatically; // Automatically publish streams
joinConfig.subscribeMode = AliRTCLinuxEngine.SubscribeMode.SubscribeAutomatically; // Automatically subscribe to streams
joinConfig.publishAvsyncMode = AliRTCLinuxEngine.PublishAvsyncMode.PublishAvsyncWithPts;
// There are two options for the audio subscription format:
// AudioFormatMixedPcm: Receives mixed PCM data for the entire channel. This triggers onSubscribeMixedAudioFrame.
// AudioFormatPcmBeforeMixing: Receives individual PCM data from each remote user. This triggers onSubscribeAudioFrame (includes uid).
joinConfig.subscribeAudioFormat = AliRTCLinuxEngine.AudioFormat.AudioFormatMixedPcm;
// Receives H.264 video frames. This triggers onRemoteVideoSample.
joinConfig.subscribeVideoFormat = AliRTCLinuxEngine.VideoFormat.VideoFormatH264;
Step 7: Join the channel
Call joinChannel with the token from Step 3 and the channel configuration.
// Call joinChannel to join the channel.
engineIns.joinChannel(authInfo, joinConfig);
Do not call joinChannel repeatedly. The generateToken method is for testing only. In production, obtain tokens from your server to prevent AppKey leaks.
Step 8: Push external video frames
Linux has no built-in camera driver. Use external input to send YUV video data to the SDK. This example loops through an I420 YUV file and pushes frames. In production, replace this with camera or decoder output.
Thread videoThread = new Thread(() -> {
int width = 720;
int height = 1280;
int fps = 15;
int frameSize = width * height * 3 / 2; // I420
try (FileInputStream fis = new FileInputStream("/tmp/test_720p.yuv")) {
byte[ ] buf = new byte[frameSize];
AliRTCLinuxEngine.VideoDataSample sample = new AliRTCLinuxEngine.VideoDataSample();
while (running && !Thread.currentThread().isInterrupted()) {
int readSize = fis.read(buf, 0, frameSize);
if (readSize != frameSize) {
fis.getChannel().position(0); // After the file is read, loop back to the beginning.
continue;
}
sample.data = buf;
sample.format = AliRTCLinuxEngine.VideoDataFormat.VideoDataFormatI420;
sample.bufferType = AliRTCLinuxEngine.VideoBufferType.VideoBufferTypeRawData;
sample.width = width;
sample.height = height;
sample.strideY = width;
sample.strideU = width / 2;
sample.strideV = width / 2;
sample.dataLen = frameSize;
sample.rotation = 0;
sample.timeStamp = System.currentTimeMillis();
int ret = engineIns.pushExternalVideoFrame(sample,
AliRTCLinuxEngine.VideoSource.VideoSourceCamera);
if (ret < 0) break;
Thread.sleep(1000 / fps);
}
} catch (IOException | InterruptedException e) {
Thread.currentThread().interrupt();
}
});
videoThread.start();
Step 9: Push external audio frames
Linux has no built-in microphone interface. Use external input to send PCM audio data to the SDK. This example loops through a PCM file (16-bit, 16 kHz, mono) and pushes frames. In production, replace this with microphone or decoder output.
Thread audioThread = new Thread(() -> {
int sampleRate = 16000;
int channels = 1;
int frameMs = 20; // 20 ms per frame
int frameSize = (sampleRate / 1000) * frameMs * 2 * channels; // int16_t = 2 bytes
try (FileInputStream fis = new FileInputStream("/tmp/test_16k_mono.pcm")) {
byte[ ] buf = new byte[frameSize];
while (running && !Thread.currentThread().isInterrupted()) {
int readSize = fis.read(buf, 0, frameSize);
if (readSize != frameSize) {
fis.getChannel().position(0); // After the file is read, loop back to the beginning.
continue;
}
int ret = engineIns.pushExternalAudioFrameRawData(buf, frameSize, 0);
if (ret != 0) {
// The SDK buffer is full. Move the file pointer back and retry later.
fis.getChannel().position(fis.getChannel().position() - readSize);
Thread.sleep(20);
continue;
}
Thread.sleep(frameMs);
}
} catch (IOException | InterruptedException e) {
Thread.currentThread().interrupt();
}
});
audioThread.start();
Step 10: Handle remote audio and video playback
Linux has no built-in playback devices. Remote audio and video data arrives through frame callbacks. Process it by writing to a file, decoding, or routing to a playback device.
Audio playback
Based on the subscribeAudioFormat setting in Step 6, one of these callbacks fires when a remote audio frame arrives:
// AudioFormatMixedPcm mode: Receives mixed PCM data for the entire channel from all remote users.
@Override
public void onSubscribeMixedAudioFrame(AliRTCLinuxEngine.AudioFrame frame) {
if (frame != null && frame.type == AliRTCLinuxEngine.AudioFrameType.AudioFrameRawPcm) {
AliRTCLinuxEngine.AudioPcmFrame pcmFrame = frame.pcm;
// pcmFrame.data PCM data (byte[], int16_t format)
// pcmFrame.channels The number of sound channels
// pcmFrame.sampleRates Sample rate
// Write to a file, send to an audio device, or decode for playback here.
}
}
// AudioFormatPcmBeforeMixing mode: Receives unmixed, individual PCM data from each user.
@Override
public void onSubscribeAudioFrame(String uid, AliRTCLinuxEngine.AudioFrame frame) {
if (frame != null && frame.type == AliRTCLinuxEngine.AudioFrameType.AudioFrameRawPcm) {
// The uid identifies which remote user the frame is from.
// Process audio data for each user separately here.
}
}
Video playback
The onRemoteVideoSample callback fires when a remote video frame arrives. The format depends on the subscribeVideoFormat setting in Step 6:
@Override
public void onRemoteVideoSample(String uid, AliRTCLinuxEngine.VideoFrame frame) {
if (frame != null && frame.type == AliRTCLinuxEngine.VideoFrameType.VideoFrameH264) {
AliRTCLinuxEngine.VideoH264Frame h264Frame = frame.h264;
// The uid identifies which remote user the frame is from.
// Write to a file, send to a renderer, or send to a video decoder here.
}
}
Step 11: Leave the channel and destroy the engine
Release resources in this order: stop stream ingest, leave the channel, then destroy the engine.
// Stop the external stream ingest threads
running = false;
videoThread.interrupt();
audioThread.interrupt();
videoThread.join(1000);
audioThread.join(1000);
// Call publishLocalVideoStream(false) / publishLocalAudioStream(false) to stop publishing.
engineIns.publishLocalVideoStream(false);
engineIns.publishLocalAudioStream(false);
// Call leaveChannel to leave the channel.
engineIns.leaveChannel();
// Wait for the onLeaveChannelResult callback before destroying the engine.
// Call release to destroy the engine (must be called after leaveChannel).
engineIns.release();
engineIns = null;
FAQ
Q: What if engine creation returns null?
Verify the AliRtcCoreService path and execute permissions:
chmod +x /path/to/Java/libs/AliRtcCoreService
Q: When should h5mode be set to true?
Only when interoperating with web clients (H5). For Linux-to-Linux communication, set it to false.
Q: How do I handle an expired token?
Listen for onAuthInfoWillExpire (token expiring soon). Regenerate the token and call the refresh interface. No need to rejoin the channel.
Listen for onAuthInfoExpired (token expired). Leave the channel and rejoin with a new token.
Q: What if a "shared library not found" error occurs at runtime?
error while loading shared libraries: libAliRtcLinuxEngine.so: cannot open shared object file
Run export LD_LIBRARY_PATH=/path/to/Java/libs:$LD_LIBRARY_PATH.