Skip to main content

SDK quickstart

The Internet of things (IoT) connects physical objects with sensors, processors, and software to enable data exchange with other devices. In related applications, such as apps for smart door bells and remote monitoring, IoT devices send or receive audio and video streams to perform their function. Agora IoT SDK gives you the ability to enable live voice and video streams on IoT devices on a variety of platforms and for many use cases, while also offering a compact SDK size, low memory usage, and low power consumption. Some typical application scenarios for IoT SDK include:

  • Real-time monitoring: Integration into smart cameras and smart doorbells enables mobile device users to receive video feed from one or more cameras.

  • Two-way audio communication between mobile devices: With IoT SDK integrated into smart watches, users perform two-way audio communication between mobile devices.

This page shows the minimum code you need to integrate audio and video communication features into your IoT devices using IoT SDK.

Understand the tech

The lightweight IoT SDK is ideal for IoT applications due to its low power consumption. The SDK provides the following performance benefits:

  • Small SDK size: The increase in app size is less than 400 KB after integration.
  • Low memory usage: When the SDK is simultaneously sending and receiving 320x240 H.264 video data, memory usage is less than 2 MB.
  • High connectivity: Under normal conditions, connectivity is greater than 99%.
  • Data transfer speed: A maximum of 50 Mbps for a user in a channel.
  • Network adaptability: Non-aware recovery from up to 50% packet loss.

The following figure shows the workflow you need to integrate IoT SDK into your app:

IoT

Prerequisites

In order to follow this procedure you must have:

  • Android Studio 4.1 or higher.
  • Android SDK API Level 24 or higher.
  • A mobile device that runs Android 4.1 or higher.
  • An Agora account and project.

  • A computer with Internet access.

    Ensure that no firewall is blocking your network communication.

  • IoT SDK for Android supports the following ABIs:

    • armeabi-v7a
    • arm64-v8a
    • x86
    • x86-64

Project setup

To integrate IoT SDK into your app, do the following:

  1. In Android Studio, create a new Phone and Tablet, Java Android project with an Empty Activity.

    After creating the project, Android Studio automatically starts gradle sync. Ensure that the sync succeeds before you continue.

  2. Add the IoT SDK to your Android project. To do this:

    1. Download the IoT SDK and extract the archive to a temporary folder <unzipped_package>.

    2. Copy the following files and folders from <unzipped_package>/agora_rtc_example_android/app/libs to your project:

      File or folderPath in your project
      Files:
      agora-rtc-sdk.jar/app/libs/
      Folders:
      arm64-v8a/app/src/main/jniLibs/
      armeabi-v7a/app/src/main/jniLibs/
      x86/app/src/main/jniLibs/
      x86_64/app/src/main/jniLibs/
    3. In Android Studio, right-click the /app/libs/agora-rtc-sdk.jar file on the navigation bar and then select Add as a library.

  3. Add permissions for network and device access.

    In /app/Manifests/AndroidManifest.xml, add the following permissions after </application>:


    _7
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    _7
    <uses-permission android:name="android.permission.INTERNET" />
    _7
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    _7
    <uses-permission android:name="android.permission.CAMERA" />
    _7
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    _7
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    _7
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

  4. Add sample audio and video files to the project resources folder:

    1. In Android Studio, right-click the res folder and choose New > Android Resource Directory.
    2. Under Resource Type select raw and click OK.
    3. In the /app/src/main/res/raw folder, copy the files send_audio_16k_1ch.pcm and send_video.h264 from the <unzipped_package>/agora_rtc_example_android/app/src/main/res/raw folder.

You are ready to add audio and video communication features to your IoT app.

Implement audio and video communication

This section shows how to use the IoT SDK to implement audio and video communication into your IoT app, step-by-step.

At startup, you initialize the engine. When a user taps a button, the app joins a channel and sends audio and video data to remote users.

Implement the user interface

In this simple interface, you create two buttons to join and leave a channel. In /app/res/layout/activity_main.xml, replace the contents of the file with the following:


_30
<?xml version="1.0" encoding="UTF-8"?>
_30
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
_30
xmlns:tools="http://schemas.android.com/tools"
_30
android:id="@+id/activity_main"
_30
android:layout_width="match_parent"
_30
android:layout_height="match_parent"
_30
tools:context=".MainActivity">
_30
_30
<RelativeLayout
_30
android:layout_width="match_parent"
_30
android:layout_height="wrap_content">
_30
_30
<Button
_30
android:id="@+id/JoinButton"
_30
android:layout_width="wrap_content"
_30
android:layout_height="wrap_content"
_30
android:layout_margin="10dp"
_30
android:onClick="joinChannel"
_30
android:text="Join" />
_30
_30
<Button
_30
android:id="@+id/LeaveButton"
_30
android:layout_width="wrap_content"
_30
android:layout_height="wrap_content"
_30
android:layout_alignTop="@id/JoinButton"
_30
android:layout_toEndOf="@id/JoinButton"
_30
android:onClick="leaveChannel"
_30
android:text="Leave" />
_30
</RelativeLayout>
_30
</ScrollView>

You see errors in your IDE. This is because the layout refers to methods that you create later.

Handle the system logic

Import the necessary Android classes and handle Android permissions.

  1. Import the Android classes

    In /app/java/com.example.<projectname>/MainActivity, add the following lines after package com.example.<project name>:


    _6
    import androidx.core.app.ActivityCompat;
    _6
    import androidx.core.content.ContextCompat;
    _6
    import android.Manifest;
    _6
    import android.content.pm.PackageManager;
    _6
    import android.view.View;
    _6
    import android.widget.Toast;

  2. Handle Android permissions

    Ensure that the permissions necessary to use audio and video features are granted. If the permissions are not granted, use the built-in Android feature to request them; if they are granted, return true.

    In /app/java/com.example.<projectname>/MainActivity, add the following lines before the onCreate method:


    _16
    private static final int PERMISSION_REQ_ID = 22;
    _16
    private static final String[] REQUESTED_PERMISSIONS =
    _16
    {
    _16
    Manifest.permission.RECORD_AUDIO,
    _16
    Manifest.permission.CAMERA
    _16
    };
    _16
    _16
    private boolean checkSelfPermission()
    _16
    {
    _16
    if (ContextCompat.checkSelfPermission(this, REQUESTED_PERMISSIONS[0]) != PackageManager.PERMISSION_GRANTED ||
    _16
    ContextCompat.checkSelfPermission(this, REQUESTED_PERMISSIONS[1]) != PackageManager.PERMISSION_GRANTED)
    _16
    {
    _16
    return false;
    _16
    }
    _16
    return true;
    _16
    }

  3. Show status updates to your users

    In /app/java/com.example.<projectname>/MainActivity, add the following lines before the onCreate method:


    _4
    void showMessage(String message) {
    _4
    runOnUiThread(() ->
    _4
    Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show());
    _4
    }

Implement the channel logic

To start Agora engine and join a channel, take the following steps:

  1. Import the IoT SDK classes

    In /app/java/com.example.<projectname>/MainActivity, add the following lines after the last import statement:


    _2
    import io.agora.rtc.AgoraRtcService;
    _2
    import io.agora.rtc.AgoraRtcEvents;

  2. Declare the variables that you use to join a channel and stream Audio/Video

    In /app/java/com.example.<projectname>/MainActivity, add the following lines to the MainActivity class:


    _11
    private final String appId = "<Your appId>"; // The App ID of your project generated on Agora Console.
    _11
    private String channelName = "<Your channel name>";
    _11
    private String token = "<Your authentication token>"; // A temp token generated on Agora Console.
    _11
    private final int uid = 0; // An integer that identifies the local user.
    _11
    private AgoraRtcService agoraEngine;
    _11
    private final String deviceId = "MyDev01";
    _11
    private boolean isJoined = false;
    _11
    private final String rtcLicense = "";
    _11
    private int connectionId;
    _11
    private AudioSendThread audioThread = null;
    _11
    private VideoSendThread videoThread = null;

  3. Setup Agora engine

    To use IoT features, you use the IoT SDK to create an AgoraRtcService instance. You then use this instance to open a connection. In /app/java/com.example.<projectname>/MainActivity, add the following code before the onCreate method:


    _26
    private void setupAgoraEngine(){
    _26
    agoraEngine = new AgoraRtcService();
    _26
    showMessage("RTC SDK version " + agoraEngine.getVersion());
    _26
    _26
    // Initialize the engine
    _26
    AgoraRtcService.RtcServiceOptions options = new AgoraRtcService.RtcServiceOptions();
    _26
    options.areaCode = AgoraRtcService.AreaCode.AREA_CODE_GLOB;
    _26
    options.productId = deviceId;
    _26
    options.licenseValue = rtcLicense;
    _26
    int ret = agoraEngine.init(appId, agoraRtcEvents, options);
    _26
    if (ret != AgoraRtcService.ErrorCode.ERR_OKAY) {
    _26
    showMessage("Fail to initialize the SDK " + ret);
    _26
    agoraEngine = null;
    _26
    return;
    _26
    } else {
    _26
    showMessage("Engine initialized");
    _26
    }
    _26
    _26
    // Create a connection
    _26
    connectionId = agoraEngine.createConnection();
    _26
    if (connectionId == AgoraRtcService.ConnectionIdSpecial.CONNECTION_ID_INVALID) {
    _26
    showMessage("Failed to createConnection");
    _26
    } else {
    _26
    showMessage("Connected");
    _26
    }
    _26
    }

  4. At startup, ensure necessary permissions and initialize the engine

    In order to send video and audio streams, you need to ensure that the local user gives permission to access the camera and microphone at startup. After the permissions are granted, you setup the IoT SDK engine.

    In /app/java/com.example.<projectname>/MainActivity, replace onCreate with the following code:


    _11
    @Override
    _11
    protected void onCreate(Bundle savedInstanceState) {
    _11
    super.onCreate(savedInstanceState);
    _11
    setContentView(R.layout.activity_main);
    _11
    _11
    // If all the permissions are granted, setup the engine
    _11
    if (!checkSelfPermission()) {
    _11
    ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, PERMISSION_REQ_ID);
    _11
    }
    _11
    setupAgoraEngine();
    _11
    }

  5. Receive and handle engine events

    The AgoraRtcService provides a number of callbacks that enable you to respond to important events. To add an event handler to your app, in /app/java/com.example.<projectname>/MainActivity, add the following lines before setupAgoraEngine:


    _103
    private final AgoraRtcEvents agoraRtcEvents = new AgoraRtcEvents() {
    _103
    @Override
    _103
    public void onJoinChannelSuccess(int connId, int uid, int elapsed_ms) {
    _103
    // Successfully joined a channel
    _103
    isJoined = true;
    _103
    showMessage("Successfully joined the channel: " + channelName);
    _103
    _103
    // Create a thread to send video frames
    _103
    videoThread = new VideoSendThread(getApplicationContext(), agoraEngine,
    _103
    channelName, connectionId);
    _103
    // Create a thread to send audio frames
    _103
    audioThread = new AudioSendThread(getApplicationContext(), agoraEngine,
    _103
    channelName, connectionId, 16000, 1, 2);
    _103
    _103
    // Start audio and video threads
    _103
    videoThread.sendStart();
    _103
    audioThread.sendStart();
    _103
    showMessage("Audio and video threads started");
    _103
    }
    _103
    _103
    @Override
    _103
    public void onConnectionLost(int connId) {
    _103
    // Connection was lost.
    _103
    }
    _103
    _103
    @Override
    _103
    public void onRejoinChannelSuccess(int connId, int uid, int elapsed_ms) {
    _103
    // The channel was successfully rejoined
    _103
    }
    _103
    _103
    @Override
    _103
    public void onLicenseValidationFailure(int connId, int error) {
    _103
    // When joining a channel, Agora will verify the License you passed in init().
    _103
    // If verification fails, the SDK will trigger this callback
    _103
    }
    _103
    _103
    @Override
    _103
    public void onError(int connId, int code, String msg) {
    _103
    // A warning callback that reports a network or media related error.
    _103
    // Normally, the app can ignore the warning message and the SDK will automatically recover.
    _103
    }
    _103
    _103
    @Override
    _103
    public void onUserJoined(int connId, int uid, int elapsed_ms) {
    _103
    // A remote user joined the channel.
    _103
    }
    _103
    _103
    @Override
    _103
    public void onUserOffline(int connId, int uid, int reason) {
    _103
    // A remote user went offline
    _103
    }
    _103
    _103
    @Override
    _103
    public void onUserMuteAudio(int connId, int uid, boolean muted) {
    _103
    // A remote user paused or resumed sending the audio stream
    _103
    }
    _103
    _103
    @Override
    _103
    public void onUserMuteVideo(int connId, int uid, boolean muted) {
    _103
    // A remote user paused or resumed sending the video stream
    _103
    }
    _103
    _103
    @Override
    _103
    public void onKeyFrameGenReq(int connId, int requestedUid, int streamType) {
    _103
    // Callback for key frame requests from remote users in the channel
    _103
    }
    _103
    _103
    @Override
    _103
    public void onAudioData(int connId, int uid, int sent_ts, byte[] data, AgoraRtcService.AudioFrameInfo info) {
    _103
    // Receive the audio frame callback of the remote user in the channel.
    _103
    // Add code here to render audio.
    _103
    }
    _103
    _103
    @Override
    _103
    public void onMixedAudioData(int connId, byte[] data, AgoraRtcService.AudioFrameInfo info) {
    _103
    // Callback for receiving audio mixing data from local and remote users in the channel.
    _103
    // This callback fires every 20 ms.
    _103
    }
    _103
    _103
    @Override
    _103
    public void onVideoData(int connId, int uid, int sent_ts, byte[] data, AgoraRtcService.VideoFrameInfo info) {
    _103
    // Receive video frame data from a remote user in the channel.
    _103
    // Add code here to render video.
    _103
    }
    _103
    _103
    @Override
    _103
    public void onTargetBitrateChanged(int connId, int targetBps) {
    _103
    // Encoder code rate update notification callback.
    _103
    // Use this callback to update the encoder bit rate.
    _103
    }
    _103
    _103
    @Override
    _103
    public void onTokenPrivilegeWillExpire(int connId, String token) {
    _103
    // The current authentication token will expire in 30 seconds
    _103
    // Get a new token and call the renewToken method
    _103
    }
    _103
    _103
    @Override
    _103
    public void onMediaCtrlReceive(int connId, int uid, byte[] payload) {
    _103
    _103
    }
    _103
    _103
    };

  6. Join a channel

    When a local user initiates a connection, call joinChannel. This method securely connects the local user to a channel using the authentication token. In /app/java/com.example.<projectname>/MainActivity, add the following code after setupAgoraEngine:


    _20
    public void joinChannel(View view) {
    _20
    // Set channel options
    _20
    AgoraRtcService.ChannelOptions channelOptions = new AgoraRtcService.ChannelOptions();
    _20
    channelOptions.autoSubscribeAudio = true;
    _20
    channelOptions.autoSubscribeVideo = true;
    _20
    channelOptions.audioCodecOpt.audioCodecType
    _20
    = AgoraRtcService.AudioCodecType.AUDIO_CODEC_TYPE_OPUS;
    _20
    channelOptions.audioCodecOpt.pcmSampleRate = 16000;
    _20
    channelOptions.audioCodecOpt.pcmChannelNum = 1;
    _20
    _20
    // Join a channel
    _20
    int ret = agoraEngine.joinChannel(connectionId, channelName,
    _20
    uid, token, channelOptions);
    _20
    if (ret != AgoraRtcService.ErrorCode.ERR_OKAY) {
    _20
    showMessage("Failed to join a channel");
    _20
    isJoined = false;
    _20
    } else {
    _20
    isJoined = true;
    _20
    }
    _20
    }

    After joining an RTC channel, you can:

    • Listen to the onAudioData callback to receive audio from users in the channel.
    • Listen to the onVideoData callback to receive video from users in the channel.
    • Call the sendAudioData method to send audio data.
    • Call the sendVideoData method to send video data.

Send audio and video data

You send audio and video data in separate threads. The sending interval should be consistent with the video frame rate and audio frame length.

To stream audio and video data:

  1. Add a StreamFile class for managing media files

    You use StreamFile to open, read, and close audio and video files. In Android Studio, select File > New > Java Class. Name the class StreamFile and replace the contents of the file with the following code:


    _73
    package <com.example.your app name>; // Should be same as your MainActivity package
    _73
    _73
    import android.content.Context;
    _73
    import android.content.res.Resources;
    _73
    import android.util.Log;
    _73
    import java.io.IOException;
    _73
    import java.io.InputStream;
    _73
    _73
    public class StreamFile {
    _73
    private final String TAG = "RTSADEMO/StreamFile";
    _73
    private InputStream mInStream = null;
    _73
    _73
    // Public Methods
    _73
    public boolean open(Context ctx, int resId) {
    _73
    try {
    _73
    Resources resources = ctx.getResources();
    _73
    mInStream = resources.openRawResource(resId);
    _73
    _73
    } catch (Resources.NotFoundException notFoundExp) {
    _73
    notFoundExp.printStackTrace();
    _73
    Log.e(TAG, "<StreamFile.open> file not found");
    _73
    return false;
    _73
    _73
    } catch (SecurityException fileSecExp) {
    _73
    fileSecExp.printStackTrace();
    _73
    Log.e(TAG, "<StreamFile.open> security exception");
    _73
    return false;
    _73
    }
    _73
    return true;
    _73
    }
    _73
    _73
    public void close() {
    _73
    if (mInStream != null) {
    _73
    try {
    _73
    mInStream.close();
    _73
    } catch (IOException fileCloseExp) {
    _73
    fileCloseExp.printStackTrace();
    _73
    }
    _73
    mInStream = null;
    _73
    }
    _73
    }
    _73
    _73
    public boolean isOpened() {
    _73
    return (mInStream != null);
    _73
    }
    _73
    _73
    public int readData(byte[] readBuffer) {
    _73
    if (mInStream == null) {
    _73
    return -2;
    _73
    }
    _73
    _73
    try {
    _73
    int readSize = mInStream.read(readBuffer);
    _73
    return readSize;
    _73
    _73
    } catch (IOException ioExp) {
    _73
    return -3;
    _73
    }
    _73
    }
    _73
    _73
    public int reset() {
    _73
    if (mInStream == null) {
    _73
    return -2;
    _73
    }
    _73
    _73
    try {
    _73
    mInStream.reset();
    _73
    return 0;
    _73
    } catch (IOException ioExp) {
    _73
    return -3;
    _73
    }
    _73
    }
    _73
    }

  2. Add a thread class to send audio

    In Android Studio, select File > New > Java Class. Name the class AudioSendThread and replace the contents of the file with the following code:


    _108
    package <com.example.your app name>; // Should be same as your MainActivity package
    _108
    _108
    import android.content.Context;
    _108
    import android.util.Log;
    _108
    _108
    import io.agora.rtc.AgoraRtcService;
    _108
    import io.agora.rtc.AgoraRtcService.AudioFrameInfo;
    _108
    import io.agora.rtc.AgoraRtcService.AudioDataType;
    _108
    _108
    public class AudioSendThread extends Thread {
    _108
    private final String TAG = "AudioSendThread";
    _108
    private final Object mExitEvent = new Object();
    _108
    private AgoraRtcService mRtcService;
    _108
    private String mChannelName;
    _108
    private int mConnectionId;
    _108
    private volatile boolean mRunning = false;
    _108
    private Context mContext;
    _108
    private final int mSampleRate, mChannelNumber, mSampleBytes;
    _108
    _108
    // Public Methods
    _108
    public AudioSendThread(Context ctx, AgoraRtcService rtcService, String chnlName,
    _108
    int connectionId, int sampleRate, int channelNumber, int sampleBytes) {
    _108
    mContext = ctx;
    _108
    mRtcService = rtcService;
    _108
    mChannelName = chnlName;
    _108
    mConnectionId = connectionId;
    _108
    mSampleRate = sampleRate;
    _108
    mChannelNumber = channelNumber;
    _108
    mSampleBytes = sampleBytes;
    _108
    }
    _108
    _108
    public boolean sendStart() {
    _108
    try {
    _108
    mRunning = true;
    _108
    this.start();
    _108
    } catch (IllegalThreadStateException exp) {
    _108
    exp.printStackTrace();
    _108
    mRunning = false;
    _108
    return false;
    _108
    }
    _108
    return true;
    _108
    }
    _108
    _108
    public void sendStop() {
    _108
    mRunning = false;
    _108
    synchronized (mExitEvent) {
    _108
    try {
    _108
    mExitEvent.wait(200);
    _108
    } catch (InterruptedException interruptExp) {
    _108
    interruptExp.printStackTrace();
    _108
    }
    _108
    }
    _108
    }
    _108
    _108
    @Override
    _108
    public void run() {
    _108
    Log.d(TAG, "<AudioSendThread.run> ==>Enter");
    _108
    StreamFile audioStream = new StreamFile();
    _108
    audioStream.open(mContext, R.raw.send_audio_16k_1ch);
    _108
    _108
    int bytesPerSec = mSampleRate*mChannelNumber*mSampleBytes;
    _108
    int bufferSize = bytesPerSec / 50; // 20ms buffer
    _108
    byte[] readBuffer = new byte[bufferSize];
    _108
    byte[] sendBuffer = new byte[bufferSize];
    _108
    _108
    while (mRunning && (audioStream.isOpened())) {
    _108
    // Read an audio frame
    _108
    int readSize = audioStream.readData(readBuffer);
    _108
    if (readSize <= 0) {
    _108
    Log.d(TAG, "<AudioSendThread.run> read audio frame EOF, reset to start");
    _108
    audioStream.reset();
    _108
    continue;
    _108
    }
    _108
    // Copy data to send buffer
    _108
    if (readSize != sendBuffer.length) {
    _108
    sendBuffer = new byte[readSize];
    _108
    }
    _108
    System.arraycopy(readBuffer, 0, sendBuffer, 0, readSize);
    _108
    _108
    // Send audio frame, data size is 20ms
    _108
    AudioFrameInfo audioFrameInfo = new AudioFrameInfo();
    _108
    audioFrameInfo.dataType = AudioDataType.AUDIO_DATA_TYPE_PCM;
    _108
    int ret = mRtcService.sendAudioData(mConnectionId, sendBuffer, audioFrameInfo);
    _108
    if (ret < 0) {
    _108
    Log.e(TAG, "<run> sendAudioData() failure, ret=" + ret);
    _108
    }
    _108
    _108
    sleepCurrThread(20); // sleep 20ms
    _108
    }
    _108
    audioStream.close();
    _108
    Log.d(TAG, "<run> <==Exit");
    _108
    _108
    // Notify: exit audio thread
    _108
    synchronized(mExitEvent) {
    _108
    mExitEvent.notify();
    _108
    }
    _108
    }
    _108
    _108
    boolean sleepCurrThread(long milliseconds) {
    _108
    try {
    _108
    Thread.sleep(milliseconds);
    _108
    } catch (InterruptedException inerruptExp) {
    _108
    inerruptExp.printStackTrace();
    _108
    return false;
    _108
    }
    _108
    return true;
    _108
    }
    _108
    }

  3. Add a thread class to send video

    In Android Studio, select File > New > Java Class. Name the class VideoSendThread and replace the contents of the file with the following code:


    _133
    package <com.example.your app name>; // Should be same as your MainActivity package
    _133
    _133
    import android.content.Context;
    _133
    import android.util.Log;
    _133
    _133
    import io.agora.rtc.AgoraRtcService;
    _133
    import io.agora.rtc.AgoraRtcService.VideoFrameInfo;
    _133
    import io.agora.rtc.AgoraRtcService.VideoDataType;
    _133
    import io.agora.rtc.AgoraRtcService.VideoFrameType;
    _133
    import io.agora.rtc.AgoraRtcService.VideoFrameRate;
    _133
    _133
    public class VideoSendThread extends Thread {
    _133
    private final String TAG = "VideoSendThread";
    _133
    private final static int[] FRAME_SIZE_ARR = {
    _133
    9654, 1617, 1884, 2003, 2362, 1887, 1773, 1943, 2081, 2000, 1975, 2093, 2247, 2469, 2578,
    _133
    2554, 2548, 2116, 2327, 2254, 2270, 1565, 2498, 2409, 2783, 2394, 2248, 1337, 1318, 1186,
    _133
    12217, 1366, 1570, 1970, 2066, 2091, 1856, 2477, 1941, 1956, 1329, 1944, 2054, 1706, 1714,
    _133
    1607, 1757, 2381, 2240, 2555, 2224, 1929, 1622, 1785, 2320, 2511, 1961, 2051, 2340, 1958,
    _133
    12223, 1605, 1690, 1950, 1848, 2130, 2177, 2539, 1868, 2043, 1942, 2188, 1974, 2272, 1716,
    _133
    2150, 1837, 2386, 2720, 2282, 2561, 2237, 1848, 1895, 2511, 2366, 2228, 1966, 1829, 2097,
    _133
    11302, 2034, 2552, 2679, 3223, 2408, 1921, 1721, 1899, 1630, 1689, 1602, 1798, 1456, 1914,
    _133
    1625, 1586, 1002, 1538, 1637, 1582, 1386, 1752, 1527, 1739, 1448, 1641, 1279, 1501, 1523,
    _133
    11903, 1057, 1504, 1495, 1917, 2051, 2237, 2169, 2437, 2315, 2162, 1870, 1962, 2034, 2141,
    _133
    1676, 1874, 2068, 2468, 2429, 2458, 2583, 2626, 1967, 2558, 2301, 2473, 2138, 2152, 1712,
    _133
    10497, 1528, 2165, 2941, 3253, 2867, 3679, 3621, 3564, 1723, 2013, 1921, 1757, 1517, 1899,
    _133
    1407, 1480, 1403, 1604, 1836, 2442, 2680, 3154, 3329, 3219, 2612, 2759, 2783, 2622, 2855,
    _133
    10619, 2145, 2259, 2513, 2779, 2757, 3199, 3081, 2684, 2977, 2884, 3170, 3346, 3164, 3102,
    _133
    3486, 3190, 2414, 2614, 2425, 2705, 3173, 3114, 2769, 2650, 2604, 2355, 2283, 2251, 2288,
    _133
    11091, 2930, 3032, 2907, 2853, 3308, 2904, 3742, 3324, 4308, 4067, 2709, 2927, 1909, 2109,
    _133
    2210, 2875, 2119, 2772, 4059, 4111, 2840, 2528, 1920, 3217, 1615, 2640, 2209, 3503, 2085,
    _133
    19570, 1705, 2747, 2420, 2553, 2435, 3508, 2084, 2188, 1994, 5324, 1771, 1397, 2608, 3201,
    _133
    2728, 2675, 3498, 1783, 1308, 2611, 2799, 3630, 2243, 1898, 2052, 3272, 2271, 2976, 3679,
    _133
    22520, 712, 1650, 1846, 2142, 1464, 1903, 1828, 2564, 1365, 1359, 1262, 3015, 2596, 2472,
    _133
    2639, 3447, 2879, 2546, 2643, 3240, 1759, 2123, 1277, 2336, 1664, 2104, 1881, 1267, 516,
    _133
    };
    _133
    _133
    private final static int FRAME_COUNT = 300;
    _133
    private final Object mExitEvent = new Object();
    _133
    private Context mContext;
    _133
    private AgoraRtcService mRtcService;
    _133
    private String mChannelName;
    _133
    private int mConnectionId;
    _133
    private volatile boolean mRunning = false;
    _133
    _133
    public VideoSendThread(Context ctx, AgoraRtcService rtcService, String chnlName, int connectionId) {
    _133
    mContext = ctx;
    _133
    mRtcService = rtcService;
    _133
    mChannelName = chnlName;
    _133
    mConnectionId = connectionId;
    _133
    }
    _133
    _133
    public boolean sendStart() {
    _133
    try {
    _133
    mRunning = true;
    _133
    this.start();
    _133
    } catch (IllegalThreadStateException exp) {
    _133
    exp.printStackTrace();
    _133
    mRunning = false;
    _133
    return false;
    _133
    }
    _133
    return true;
    _133
    }
    _133
    _133
    public void sendStop() {
    _133
    mRunning = false;
    _133
    synchronized (mExitEvent) {
    _133
    try {
    _133
    mExitEvent.wait(200);
    _133
    } catch (InterruptedException interruptExp) {
    _133
    interruptExp.printStackTrace();
    _133
    }
    _133
    }
    _133
    }
    _133
    _133
    @Override
    _133
    public void run() {
    _133
    Log.d(TAG, "<run> ==>Enter");
    _133
    StreamFile videoStream = new StreamFile();
    _133
    videoStream.open(mContext, R.raw.send_video);
    _133
    _133
    int frameIndex = 0;
    _133
    while (mRunning && (videoStream.isOpened())) {
    _133
    // Read a video frame
    _133
    if (frameIndex >= FRAME_COUNT) {
    _133
    Log.d(TAG, "<run> read video frame EOF, reset to start");
    _133
    frameIndex = 0;
    _133
    videoStream.reset();
    _133
    }
    _133
    int frameSize = FRAME_SIZE_ARR[frameIndex];
    _133
    byte[] videoBuffer = new byte[frameSize];
    _133
    int readSize = videoStream.readData(videoBuffer);
    _133
    if (readSize <= 0) {
    _133
    Log.e(TAG, "<run> read video frame error, readSize=" + readSize);
    _133
    }
    _133
    _133
    // Send a video frame
    _133
    int streamId = 0;
    _133
    VideoFrameInfo videoFrameInfo = new VideoFrameInfo();
    _133
    videoFrameInfo.dataType = VideoDataType.VIDEO_DATA_TYPE_H264;
    _133
    videoFrameInfo.frameType = VideoFrameType.VIDEO_FRAME_KEY;
    _133
    videoFrameInfo.frameRate = VideoFrameRate.VIDEO_FRAME_RATE_FPS_15;
    _133
    int ret = mRtcService.sendVideoData(mConnectionId, videoBuffer, videoFrameInfo);
    _133
    if (ret < 0) {
    _133
    Log.e(TAG, "<VideoSendThread.run> sendVideoData() failure, ret=" + ret
    _133
    + ", dataSize=" + videoBuffer.length);
    _133
    }
    _133
    _133
    frameIndex++;
    _133
    videoBuffer = null;
    _133
    _133
    sleepCurrThread(66);
    _133
    }
    _133
    _133
    videoStream.close();
    _133
    Log.d(TAG, "<run> <==Exit");
    _133
    _133
    // Notify: exit video thread
    _133
    synchronized(mExitEvent) {
    _133
    mExitEvent.notify();
    _133
    }
    _133
    }
    _133
    _133
    boolean sleepCurrThread(long milliseconds) {
    _133
    try {
    _133
    Thread.sleep(milliseconds);
    _133
    } catch (InterruptedException inerruptExp) {
    _133
    inerruptExp.printStackTrace();
    _133
    return false;
    _133
    }
    _133
    return true;
    _133
    }
    _133
    _133
    }

Stop your app

In this implementation, you initiate and destroy the engine when the app opens and closes. The local user joins and leaves a channel using the same engine instance. To elegantly exit your app:

  1. Leave the channel when a user ends the call

    When a user presses the Leave button, use leaveChannel to exit the channel. In /app/java/com.example.<projectname>/MainActivity, add leaveChannel after joinChannel:


    _26
    public void leaveChannel(View view) {
    _26
    if (!isJoined) {
    _26
    showMessage("Join a channel first");
    _26
    _26
    } else {
    _26
    // Stop audio and threads
    _26
    if (videoThread != null) {
    _26
    videoThread.sendStop();
    _26
    videoThread = null;
    _26
    }
    _26
    if (audioThread != null) {
    _26
    audioThread.sendStop();
    _26
    audioThread = null;
    _26
    }
    _26
    showMessage("Audio and video threads stopped");
    _26
    _26
    // Leave the channel
    _26
    int ret = agoraEngine.leaveChannel(connectionId);
    _26
    if (ret != AgoraRtcService.ErrorCode.ERR_OKAY) {
    _26
    showMessage("Failed to leave the channel");
    _26
    } else {
    _26
    isJoined = false;
    _26
    showMessage("Left the channel " + channelName);
    _26
    }
    _26
    }
    _26
    }

  2. Release the resources used by your app

    When a user closes the app, use onDestroy to release all the resources in use. In /app/java/com.example.<projectname>/MainActivity, add onDestroy after onCreate:


    _10
    protected void onDestroy() {
    _10
    super.onDestroy();
    _10
    _10
    // Destroy the connection
    _10
    agoraEngine.destroyConnection(connectionId);
    _10
    // Release all resources allocated to the SDK by the init() method
    _10
    agoraEngine.fini();
    _10
    // Invalidate the connection ID
    _10
    connectionId = AgoraRtcService.ConnectionIdSpecial.CONNECTION_ID_INVALID;
    _10
    }

Test your implementation

To test your implementation, take the following steps:

  1. Generate a temporary token in Agora Console.

  2. In your browser, navigate to the Agora web demo and update App ID, Channel, and Token with the values for your temporary token, then click Join.

  1. In Android Studio, in app/java/com.example.<projectname>/MainActivity, update appId, channelName, and token with the values for your temporary token.

  2. Connect a physical Android device to your development device.

  3. In Android Studio, click Run app. A moment later you see the project installed on your device.

    If this is the first time you run the project, you need to grant microphone and camera access to your app.

  4. Click Join to join a channel.

    You hear audio and see a video playing in the web demo app, pushed from the IoT SDK demo project.

  5. Click Leave.

    Audio and video streaming stops and you exit the channel.

Reference

This section contains information that completes the information in this page, or points you to documentation that explains other aspects to this product.