Quickstart
This page provides a step-by-step guide on how to create a basic Voice Calling app using the Agora Voice SDK.
Understand the tech
To start a Voice Calling session, implement the following steps in your app:
-
Initialize the Agora Engine: Before calling other APIs, create and initialize an Agora Engine instance.
-
Join a channel: Call methods to create and join a channel.
- Send and receive audio: All users can publish streams to the channel and subscribe to audio streams published by other users in the channel.
Prerequisites
- Android Studio 4.2 or higher.
- Android SDK API Level 21 or higher.
- Two mobile devices running Android 5.0 or higher.
- A microphone
- A valid Agora account and project. Please refer to Agora account management for details.
Set up your project
This section shows you how to set up your Android project and install the Agora Voice SDK.
- Create a new project
- Add to an existing project
-
Create a new project.
- Open Android Studio and select File > New > New Project....
- Select Phone and Tablet > Empty Activity and click Next.
- Set the project name and storage path.
- Select Java or Kotlin as the language, and click Finish to create the Android project.
NoteAfter you create a project, Android Studio automatically starts gradle sync. Ensure that the synchronization is successful before proceeding to the next step.
-
Add a new activity to your project.
- Open your project in Android Studio.
- Right-click on the
app/src/main/java/<your.package.name>
folder. - Select New → Activity → Empty Activity.
- Enter an activity name and click Finish.
This guide uses
MainActivity
as the activity name in the sample code. Replace it with your activity name where required.
-
Add a layout file for your activity.
Set up a basic layout for your activity. Refer to Create a user interface to get a bare bones sample layout.
Install the SDK
Use either of the following methods to add Voice SDK to your project.
- Maven Central
- Manual integration
-
Open the
settings.gradle
file in the project's root directory and add the Maven Central dependency, if it doesn't already exist:infoIf your Android project uses dependencyResolutionManagement, the method of adding the Maven Central dependency may differ.
-
To integrate the Voice SDK into your Android project, add the following to the
dependencies
block in your project modulebuild.gradle
file:-
Groovy
build.gradle
implementation 'io.agora.rtc:voice-sdk:x.y.z'
-
Kotlin
build.gradle.kts
implementation("io.agora.rtc:voice-sdk:x.y.z")
Replace
x.y.z
with the specific SDK version number, such as4.5.0
.infoTo get the latest version number, check the Release notes. To integrate the Lite SDK, use
io.agora.rtc:lite-sdk
instead. -
-
Prevent code obfuscation
Open the
/app/proguard-rules.pro
file and add the following lines to prevent the Voice SDK code from being obfuscated:-keep class io.agora.** { *; }
-dontwarn io.agora.**
-
Download the latest version of Voice SDK from the the SDKs page and unzip it.
-
Open the unzipped file and copy the following files or subfolders to your project path.
File or folder Project path agora-rtc-sdk.jar
file/app/libs/
arm64-v8a
folder/app/src/main/jniLibs/
armeabi-v7a
folder/app/src/main/jniLibs/
x86
folder/app/src/main/jniLibs/
x86_64
folder/app/src/main/jniLibs/
high_level_api
ininclude
folder/app/src/main/jniLibs/
-
Select the file
/app/libs/agora-rtc-sdk.jar
in the left navigation bar of Android Studio project files, right-click, and select add as a library from the drop-down menu. -
Prevent code obfuscation
Open the
/app/proguard-rules.pro
file and add the following lines to prevent the Voice SDK code from being obfuscated:-keep class io.agora.** { *; }
-dontwarn io.agora.**
Implement Voice Calling
This section guides you through the implementation of basic real-time audio interaction in your app.
The following figure illustrates the essential steps:
Quick start sequence
This guide includes complete sample code that demonstrates implementing basic real-time interaction. To understand the core API calls in the sample code, review the following implementation steps and use the code in your MainActivity
file.
Import Agora classes
Import the relevant Agora classes and interfaces:
- Java
- Kotlin
import io.agora.rtc2.Constants;import io.agora.rtc2.IRtcEngineEventHandler;import io.agora.rtc2.RtcEngine;import io.agora.rtc2.RtcEngineConfig;import io.agora.rtc2.ChannelMediaOptions;
import io.agora.rtc2.ChannelMediaOptionsimport io.agora.rtc2.Constantsimport io.agora.rtc2.IRtcEngineEventHandlerimport io.agora.rtc2.RtcEngineimport io.agora.rtc2.RtcEngineConfig
Initialize the engine
For real-time communication, initialize an RtcEngine
instance and set up event handlers to manage user interactions within the channel. Use RtcEngineConfig
to specify the application context, App ID, and custom event handler, then call RtcEngine.create(config)
to initialize the engine, enabling further channel operations. In your MainActivity
file, add the following code:
- Java
- Kotlin
// Fill in the app ID from Agora Consoleprivate String myAppId = "<Your app ID>";private RtcEngine mRtcEngine;private void initializeAgoraVoiceSDK() { try { RtcEngineConfig config = new RtcEngineConfig(); config.mContext = getBaseContext(); config.mAppId = myAppId; config.mEventHandler = mRtcEventHandler; mRtcEngine = RtcEngine.create(config); } catch (Exception e) { throw new RuntimeException("Error initializing RTC engine: " + e.getMessage()); }}
// Fill in the App ID obtained from the Agora Consoleprivate val myAppId = "<Your app ID>"private var mRtcEngine: RtcEngine? = nullprivate fun initializeAgoraVoiceSDK() { try { val config = RtcEngineConfig().apply { mContext = baseContext mAppId = myAppId mEventHandler = mRtcEventHandler } mRtcEngine = RtcEngine.create(config) } catch (e: Exception) { throw RuntimeException("Error initializing RTC engine: ${e.message}") }}
Join a channel
To join a channel, call joinChannel
with the following parameters:
-
Channel name: The name of the channel to join. Clients that pass the same channel name join the same channel. If a channel with the specified name does not exist, it is created when the first user joins.
-
Authentication token: A dynamic key that authenticates a user when the client joins a channel. In a production environment, you obtain a token from a token server in your security infrastructure. For the purpose of this guide Generate a temporary token.
-
User ID: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to
0
when joining a channel, the SDK generates a random number for the user ID and returns the value in theonJoinChannelSuccess
callback. -
Channel media options: Configure
ChannelMediaOptions
to define publishing and subscription settings, optimize performance for your specific use-case, and set optional parameters.
For Voice Calling, set the channelProfile
to CHANNEL_PROFILE_COMMUNICATION
and the user role to CLIENT_ROLE_BROADCASTER
.
- Java
- Kotlin
// Fill in the channel nameprivate String channelName = "<Your channel name>";// Fill in the temporary token generated from Agora Consoleprivate String token = "<Your token>";private void joinChannel() { ChannelMediaOptions options = new ChannelMediaOptions(); options.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER; options.channelProfile = Constants.CHANNEL_PROFILE_COMMUNICATION; options.publishCameraTrack = true; mRtcEngine.joinChannel(token, channelName, 0, options);}
// Fill in the channel nameprivate val channelName = "<Your channel name>"// Fill in the temporary token generated from Agora Consoleprivate val token = "<Your token>"private fun joinChannel() { val options = ChannelMediaOptions().apply { clientRoleType = Constants.CLIENT_ROLE_BROADCASTER channelProfile = Constants.CHANNEL_PROFILE_COMMUNICATION publishMicrophoneTrack = true; } mRtcEngine?.joinChannel(token, channelName, 0, options)}
Subscribe to Voice SDK events
The Voice SDK provides an interface for subscribing to channel events. To use it, create an instance of IRtcEngineEventHandler
and implement the event methods you want to handle.
To ensure that you receive all Voice SDK events, set the Agora Engine event handler before joining a channel.
- Java
- Kotlin
private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() { // Callback when successfully joining the channel @Override public void onJoinChannelSuccess(String channel, int uid, int elapsed) { super.onJoinChannelSuccess(channel, uid, elapsed); showToast("Joined channel " + channel); } // Callback when a remote user or host joins the current channel @Override public void onUserJoined(int uid, int elapsed) { super.onUserJoined(uid, elapsed); runOnUiThread(() -> { showToast("User joined: " + uid); // Show toast for user joining }); } // Callback when a remote user or host leaves the current channel @Override public void onUserOffline(int uid, int reason) { super.onUserOffline(uid, reason); runOnUiThread(() -> { showToast("User offline: " + uid); // Show toast for user going offline }); }};
private val mRtcEventHandler = object : IRtcEngineEventHandler() { override fun onJoinChannelSuccess(channel: String?, uid: Int, elapsed: Int) { super.onJoinChannelSuccess(channel, uid, elapsed) runOnUiThread { showToast("Joined channel $channel") } } override fun onUserJoined(uid: Int, elapsed: Int) { runOnUiThread { showToast("User joined: $uid") } } override fun onUserOffline(uid: Int, reason: Int) { super.onUserOffline(uid, reason) runOnUiThread { showToast("User offline: $uid") } }}
Handle permissions
To access the microphone on Android devices, declare the necessary permissions in the app's manifest and ensure that the user grants these permissions when the app starts.
-
Open your project's
AndroidManifest.xml
file and add the following permissions before<application>
: -
Use the following code to handle runtime permissions in your Android app. The logic ensures that the necessary permissions are granted before starting Voice Calling. In your
MainActivity
file, add the following code:- Java
- Kotlin
private static final int PERMISSION_REQ_ID = 22; private boolean checkPermissions() { for (String permission : getRequiredPermissions()) { if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { return false; } } return true; } private void requestPermissions() { ActivityCompat.requestPermissions(this, getRequiredPermissions(), PERMISSION_REQ_ID); } private String[] getRequiredPermissions() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { return new String[]{ Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_PHONE_STATE, Manifest.permission.BLUETOOTH_CONNECT }; } else { return new String[]{Manifest.permission.RECORD_AUDIO}; } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (checkPermissions()) { startVoiceCalling(); } }
private val permissionReqId = 22 private fun checkPermissions(): Boolean { for (permission in getRequiredPermissions()) { if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { return false } } return true } private fun requestPermissions() { ActivityCompat.requestPermissions(this, getRequiredPermissions(), PERMISSION_REQ_ID) } private fun getRequiredPermissions(): Array<String> { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { arrayOf( Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_PHONE_STATE, Manifest.permission.BLUETOOTH_CONNECT ) } else { arrayOf(Manifest.permission.RECORD_AUDIO) } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (checkPermissions()) { startVoiceCalling() } }
Start and close the app
When a user launches your app, start real-time interaction. When a user closes the app, stop the interaction.
-
In the
onCreate
callback, check whether the app has been granted the required permissions. If the permissions have not been granted, request the required permissions from the user. If permissions are granted, initializeRtcEngine
and join a channel.- Java
- Kotlin
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (checkPermissions()) { startVoiceCalling(); } else { requestPermissions(); } }
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) if (checkPermissions()) { startVoiceCalling() } else { requestPermissions() } }
-
When a user closes the app, or switches the app to the background, call
leaveChannel
to leave the current channel and release all session-related resources.- Java
- Kotlin
private void cleanupAgoraEngine() { if (mRtcEngine != null) { mRtcEngine.leaveChannel(); mRtcEngine = null; }}
private fun cleanupAgoraEngine() { mRtcEngine?.apply { leaveChannel() } mRtcEngine = null }
Complete sample code
A complete code sample demonstrating the basic process of real-time interaction is provided for your reference. To use the sample code, copy the following lines into the MainActivity
file in your project. Then, replace <projectname>
in package com.example.<projectname>
with your project's name.
Complete sample code for real-time Voice Calling
- Java
- Kotlin
package com.example.<projectname>;import android.Manifest;import android.content.pm.PackageManager;import android.os.Build;import android.os.Bundle;import android.widget.Toast;import androidx.annotation.NonNull;import androidx.appcompat.app.AppCompatActivity;import androidx.core.app.ActivityCompat;import androidx.core.content.ContextCompat;import io.agora.rtc2.ChannelMediaOptions;import io.agora.rtc2.Constants;import io.agora.rtc2.IRtcEngineEventHandler;import io.agora.rtc2.RtcEngine;import io.agora.rtc2.RtcEngineConfig;public class MainActivity extends AppCompatActivity { private static final int PERMISSION_REQ_ID = 22; private final String myAppId = "<Your app ID>"; private final String channelName = "<Your channel name>"; private final String token = "<Your token>"; private RtcEngine mRtcEngine; private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() { // Callback when successfully joining the channel @Override public void onJoinChannelSuccess(String channel, int uid, int elapsed) { super.onJoinChannelSuccess(channel, uid, elapsed); showToast("Joined channel " + channel); } // Callback when a remote user or host joins the current channel @Override public void onUserJoined(int uid, int elapsed) { super.onUserJoined(uid, elapsed); runOnUiThread(() -> { showToast("User joined: " + uid); // Show toast for user joining }); } // Callback when a remote user or host leaves the current channel @Override public void onUserOffline(int uid, int reason) { super.onUserOffline(uid, reason); runOnUiThread(() -> { showToast("User offline: " + uid); // Show toast for user going offline }); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (checkPermissions()) { startVoiceCalling(); } else { requestPermissions(); } } private boolean checkPermissions() { for (String permission : getRequiredPermissions()) { if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { return false; } } return true; } private void requestPermissions() { ActivityCompat.requestPermissions(this, getRequiredPermissions(), PERMISSION_REQ_ID); } private String[] getRequiredPermissions() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { return new String[]{ Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_PHONE_STATE, Manifest.permission.BLUETOOTH_CONNECT }; } else { return new String[]{Manifest.permission.RECORD_AUDIO}; } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (checkPermissions()) { startVoiceCalling(); } } private void startVoiceCalling() { initializeAgoraVoiceSDK(); joinChannel(); } private void initializeAgoraVoiceSDK() { try { RtcEngineConfig config = new RtcEngineConfig(); config.mContext = getApplicationContext(); config.mAppId = myAppId; config.mEventHandler = mRtcEventHandler; mRtcEngine = RtcEngine.create(config); } catch (Exception e) { throw new RuntimeException("Error initializing RTC engine: " + e.getMessage()); } } private void joinChannel() { ChannelMediaOptions options = new ChannelMediaOptions(); options.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER; options.channelProfile = Constants.CHANNEL_PROFILE_COMMUNICATION; options.publishMicrophoneTrack = true; mRtcEngine.joinChannel(token, channelName, 0, options); } @Override protected void onDestroy() { super.onDestroy(); cleanupAgoraEngine(); } private void cleanupAgoraEngine() { if (mRtcEngine != null) { mRtcEngine.leaveChannel(); mRtcEngine = null; } } private void showToast(String message) { runOnUiThread(() -> Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show()); }}
package com.example.<projectname>import android.Manifestimport android.content.pm.PackageManagerimport android.os.Buildimport android.os.Bundleimport android.widget.Toastimport androidx.appcompat.app.AppCompatActivityimport androidx.core.app.ActivityCompatimport androidx.core.content.ContextCompatimport io.agora.rtc2.ChannelMediaOptionsimport io.agora.rtc2.Constantsimport io.agora.rtc2.IRtcEngineEventHandlerimport io.agora.rtc2.RtcEngineimport io.agora.rtc2.RtcEngineConfigclass MainActivity : AppCompatActivity() { private val PERMISSION_REQ_ID = 22 private val myAppId = "<Your app ID>" private val channelName = "<Your channel name>" private val token = "<Your token>" private var mRtcEngine: RtcEngine? = null private val mRtcEventHandler = object : IRtcEngineEventHandler() { override fun onJoinChannelSuccess(channel: String?, uid: Int, elapsed: Int) { super.onJoinChannelSuccess(channel, uid, elapsed) runOnUiThread { showToast("Joined channel $channel") } } override fun onUserJoined(uid: Int, elapsed: Int) { runOnUiThread { showToast("A user joined") } } override fun onUserOffline(uid: Int, reason: Int) { super.onUserOffline(uid, reason) runOnUiThread { showToast("User offline: $uid") } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) if (checkPermissions()) { startVoiceCalling() } else { requestPermissions() } } private fun checkPermissions(): Boolean { for (permission in getRequiredPermissions()) { if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { return false } } return true } private fun requestPermissions() { ActivityCompat.requestPermissions(this, getRequiredPermissions(), PERMISSION_REQ_ID) } private fun getRequiredPermissions(): Array<String> { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { arrayOf( Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_PHONE_STATE, Manifest.permission.BLUETOOTH_CONNECT ) } else { arrayOf(Manifest.permission.RECORD_AUDIO) } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (checkPermissions()) { startVoiceCalling() } } private fun startVoiceCalling() { initializeAgoraVoiceSDK() joinChannel() } private fun initializeAgoraVoiceSDK() { try { val config = RtcEngineConfig().apply { mContext = baseContext mAppId = myAppId mEventHandler = mRtcEventHandler } mRtcEngine = RtcEngine.create(config) } catch (e: Exception) { throw RuntimeException("Error initializing RTC engine: ${e.message}") } } private fun joinChannel() { val options = ChannelMediaOptions().apply { clientRoleType = Constants.CLIENT_ROLE_BROADCASTER channelProfile = Constants.CHANNEL_PROFILE_COMMUNICATION publishMicrophoneTrack = true } mRtcEngine?.joinChannel(token, channelName, 0, options) } override fun onDestroy() { super.onDestroy() cleanupAgoraEngine() } private fun cleanupAgoraEngine() { mRtcEngine?.apply { stopPreview() leaveChannel() } mRtcEngine = null } private fun showToast(message: String) { runOnUiThread { Toast.makeText(this@MainActivity, message, Toast.LENGTH_SHORT).show() } }}
For the myAppId
and token
variables, replace the placeholders with the values you obtained from Agora Console. Ensure you enter the same channelName
you used when generating the temporary token.
Create a user interface
Use the following code to generate a basic user interface. Paste the code into the /app/src/main/res/layout/activity_main.xml
file, replacing the existing content.
Sample code to create the user interface
<?xml version="1.0" encoding="UTF-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/darker_gray" tools:context=".MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Welcome to Agora Voice Calling." /> </RelativeLayout>
Test the sample code
Take the following steps to test the sample code:
-
In
MainActivity
update the values formyAppId
, andtoken
with values from Agora Console. Fill in the samechannelName
you used to generate the token. -
Enable developer options on your Android test device. Turn on USB debugging, connect the Android device to your development machine through a USB cable, and check that your device appears in the Android device options.
-
In Android Studio, click
Sync Project with Gradle Files to resolve project dependencies and update the configuration.
-
After synchronization is successful, click
Run app. Android Studio starts compilation. After a few moments, the app is installed on your Android device.
- Launch the App, grant the recording permission.
-
On a second Android device, repeat the previous steps to install and launch the app. Alternatively, use the Web demo to join the same channel and test the following use-cases:
- If users on both devices join the channel as hosts, they can hear each other.
- If one user joins as host and the other as audience, the audience can hear the host.
Reference
This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.
- If a firewall is deployed in your network environment, refer to Connect with Cloud Proxy to use Agora services normally.
Next steps
After implementing the quickstart sample, read the following documents to learn more:
- To ensure communication security in a test or production environment, best practice is to obtain and use a token from an authentication server. For details, see Secure authentication with tokens.
Sample project
Agora provides open source sample projects on GitHub for your reference. Download or view the JoinChannelAudio project for a more detailed example.