Zephyrnet Logo

Build a Streaming Application with Face Filter on Android

Date:

Shaocheng Yang
  1. A basic-to-intermediate understanding of Java and Android SDK
  2. Agora.io account
  3. Banuba Face AR demo app
  4. Android Studio and 2 Android devices

This guide will go over the steps for building a live streaming application on Android using the Agora SDK. This is a list of the core features that will be included in our app:

  • Users can set up virtual rooms to host live streams and become a streamer.
  • Users can find all live streams and join the virtual room as an audience.
  • Streamers can use the face filter feature to stream with a virtual mask or animation.
  • Streamers can change their voice through a voice changer.
  • Audiences in a virtual room can send text messages which can be seen by everyone in that room.
  • Users can search for other users by their name and send private text messages to them.

It is easier to build our application on top of the Banuba FaceAR demo apps. The package contains a default Banuba SDK Demo app demonstrating major Banuba SDK features and a beautification example app with neural networks coloring feature. We are going to focus on the Banuba SDK Demo app for our project. Feel free to explore the beautification example app on your own.

https://docs.banuba.com/docs/android/android_demo_app_ui

1. How to use subtle AR filters to survive your Zoom meetings?

2. The First No-Headset Virtual Monitor

3. Augmented reality (AR) is the future of Restaurant Menu?

4. Creating remote MR productions

To integrate Agora SDK in your project, you need to add the following lines in the /app/build.gradle file of the project.

//Agora RTC SDK for video call
implementation 'io.agora.rtc:full-sdk:3.0.1.1'//Agora RTM SDK for chat messaging
implementation 'io.agora.rtm:rtm-sdk:1.2.2'
//Agora UIKit
implementation 'io.agora.uikit:agorauikit:2.0.1'

Add Project Permissions

The next thing we need to do is to add project permissions in the /app/src/main/AndroidManifest.xml file for device access according to your needs:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- The Agora SDK requires Bluetooth permissions in case users are using Bluetooth devices. -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
</manifest>

Since our application allows users to search and chat with other users in the app, we need to use Firebase Realtime Database to save users’ account information. Here are the steps you need to connect your application to Firebase Realtime Database:

First, let’s build our application from the first page. Same as most of the streaming applications, our application needs a user login page. And that should look like this:

public void onLoginNextClick(View view) {
EditText userNameEditText = findViewById(R.id.et_login_user_name);
String userName = userNameEditText.getText().toString();

if(userName == null || userName.equals("")) {
Toast.makeText(this, "user name cannot be empty", Toast.LENGTH_SHORT).show();
}else {
Intent intent = new Intent(this, HomeActivity.class);
intent.putExtra("userName", userName);
startActivity(intent);
}
}

In the HomeActivity, we will create a three-page bottom navigation bar with a fragment connected to each page. If the user taps on the first bottom navigation menu, he will be directed to the Live fragment. Similarly, the second menu will direct the user to the Chat fragment and the last one is the Other fragment.

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
userName = getIntent().getStringExtra("userName");

saveUserOnFireDatabase(userName);
}

private void saveUserOnFireDatabase(String userName) {
mRef = FirebaseDatabase.getInstance().getReference("Users");
mRef.push();
mRef.child(userName).setValue(new DBUser(userName, false));
}

Live Fragment

The Live fragment is to show all the live streamers’ names and allow other users to join the streamer’s virtual room by clicking a button.

childEventListener = new ChildEventListener() {
@Override
public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
DBUser result = dataSnapshot.getValue(DBUser.class);
if (!result.getName().equals(userName) && result.getStreaming() == true) {
streamerList.add(result);
}
}

...
};

mRef.orderByChild("name").addChildEventListener(childEventListener);

private void joinStream(String streamerName) {
Intent intent = new Intent(getActivity(), AudienceActivity.class);
intent.putExtra("streamerName", streamerName);
intent.putExtra("userName", userName);
startActivity(intent);
}
@Override
public void onClick(View v) {
Intent intent = new Intent(getActivity(), MainActivity.class);
intent.putExtra("userName", userName);
startActivity(intent);
}

Chat Fragment

The Chat fragment is the place for users to search for other users and send private text messages to them. So you will build your UI like this:

Let’s open the MainActivity class. From the previous steps, we know this activity is for adding the face filter features on the local camera views. Our job here is to send these local camera views with face filters out to the remote users.

Initialize RtcEngine and Join Channel

Inside the onCreate callback, get the username passed from the home activity and initialize the Agora RtcEngine.

userName = getIntent().getStringExtra("userName");

try {
mRtcEngine = RtcEngine.create(getBaseContext(), getString(appID), mRtcEventHandler);
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
throw new RuntimeException("NEED TO check rtc sdk init fatal errorn" + Log.getStackTraceString(e));
}

mRtcEngine.enableVideo();

  1. Click the Project Management tab on the left navigation panel.
  2. Click “Create” and follow the on-screen instructions to set the project name, choose an authentication mechanism, and click “Submit”.
  3. On the Project Management page, find the App ID of your project.
mRtcEngine.setExternalVideoSource(true, false, true);
mRtcEngine.joinChannel(token, channelName, "Extra Info", 0);

Push External Video Frame

We have already let the Agora RtcEngine know that we will use the external video source as the video input. However, we haven’t provided the video frame yet.

mSdkManager.startForwardingFrames();
@Override
public void onFrameRendered(@NonNull Data data, int width, int height) {
byte[] arr = new byte[data.data.remaining()];
data.data.get(arr);

AgoraVideoFrame agoraVideoFrame = new AgoraVideoFrame();
agoraVideoFrame.buf = arr;
agoraVideoFrame.stride = width;
agoraVideoFrame.height = height;
agoraVideoFrame.format = AgoraVideoFrame.FORMAT_RGBA;
agoraVideoFrame.timeStamp = System.currentTimeMillis();
mRtcEngine.pushExternalVideoFrame(agoraVideoFrame);
data.close();
}

Add the Voice Changer

To make the streaming experience more interesting, we can add a voice changer in the application. Create a button in the MainActivity and set the onClick method for that. When the user clicks that, we will call setLocalVoiceChanger to set the voice changer.

public void onVoiceChangerClick(View view) {
mRtcEngine.setLocalVoiceChanger(VOICE_CHANGER_BABYGIRL);
Toast.makeText(this, "Voice Changer ON", Toast.LENGTH_SHORT).show();
}

Setting up the audience view is easier. Let’s go to the Audience activity and use the Agora UIKit.

@Override
protected void onCreate(Bundle savedInstanceState) {
...
AgoraRTC.instance().bootstrap(this, appID, channelName);
setContentView(R.layout.group_template);
}
AgoraRTC.instance().muteLocalVideoStream(true);
AgoraRTC.instance().muteLocalAudioStream(true);
IRtcEngineEventHandler eventHandler = new IRtcEngineEventHandler() {

@Override
public void onRemoteVideoStateChanged(final int uid, int state, int reason, int elapsed) {
super.onRemoteVideoStateChanged(uid, state, reason, elapsed);

if (state == REMOTE_VIDEO_STATE_STARTING) {
runOnUiThread(new Runnable() {
@Override
public void run() {
agoraView.setUID(uid);
}
});
}
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
...
agoraView = findViewById(R.id.max_view);
AgoraRTC.instance().registerListener(eventHandler);
...
}
endCallButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});

Text messaging in a streaming application can be divided into two parts: channel messaging and private messaging. The first one sends messages to the channel and everyone in the channel can receive that message while the second one sends private messages from a peer to another peer.

Channel Messaging

We want to allow all the audiences to send messages in the streamer’s room. To achieve that, we need to create a RtmClient using Agora Messaging SDK.

mRtmclient = RtmClient.createInstance(mContext, appID, new RtmClientListener() {
@Override
public void onConnectionStateChanged(int state, int reason){
for (RtmClientListener listener : mListenerList) {
listener.onConnectionStateChanged(state, reason);
}
}@Override
public void onMessageReceived(RtmMessage rtmMessage, String peerId) { ... }
});
mRtmClient.login(null, userName, new io.agora.rtm.ResultCallback<Void>() {
@Override
public void onSuccess(Void aVoid) {
...
}
@Override
public void onFailure(ErrorInfo errorInfo) {
...
}
});
mRtmChannel = mRtmClient.createChannel(mChannelName, new MyChannelListener());
class MyChannelListener implements RtmChannelListener {
@Override
public void onMessageReceived(final RtmMessage message, final RtmChannelMember fromMember) {
runOnUiThread(new Runnable() {
@Override
public void run() {
String account = fromMember.getUserId();
String msg = message.getText();
MessageBean messageBean = new MessageBean(account, msg, false);
messageBean.setBackground(getMessageColor(account));
mMessageBeanList.add(messageBean);
mMessageAdapter.notifyItemRangeChanged(mMessageBeanList.size(), 1);
mRecyclerView.scrollToPosition(mMessageBeanList.size() - 1);
}
});
}
...
}
mRtmChannel.join(new ResultCallback<Void>() {
@Override
public void onSuccess(Void responseInfo) { ... }@Override
public void onFailure(ErrorInfo errorInfo) { ... }
});
private void sendChannelMessage(String content) {
// step 1: create a message
RtmMessage message = mRtmClient.createMessage();
message.setText(content);
// step 2: send message to channel
mRtmChannel.sendMessage(message, new ResultCallback<Void>() {
@Override
public void onSuccess(Void aVoid) {
...
}

@Override
public void onFailure(ErrorInfo errorInfo) {
...
}
});
}

Private Messaging

In the ChatFragment, users can find their friend by searching the friend’s name and send private text messages to them.

childEventListener = new ChildEventListener() {
@Override
public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
DBUser result = dataSnapshot.getValue(DBUser.class);
showFriendText.setText(result.getName());
mRef.orderByChild("name").startAt(friendName).endAt(friendName + "uf8ff").removeEventListener(childEventListener);
}
...
};

mRef.orderByChild("name").startAt(friendName).endAt(friendName + "uf8ff").addChildEventListener(childEventListener);

  1. You need to register a RtmClientListener and receive messages from the onMessageReceived callback.
class MyRtmClientListener implements RtmClientListener {

@Override
public void onMessageReceived(final RtmMessage message, final String peerId) {
runOnUiThread(new Runnable() {
@Override
public void run() {
String content = message.getText();
if (peerId.equals(mPeerId)) {
MessageBean messageBean = new MessageBean(peerId, content,false);
messageBean.setBackground(getMessageColor(peerId));
mMessageBeanList.add(messageBean);
mMessageAdapter.notifyItemRangeChanged(mMessageBeanList.size(), 1);
mRecyclerView.scrollToPosition(mMessageBeanList.size() - 1);
} else {
MessageUtil.addMessageBean(peerId, content);
}
}
});
}

...
}

mRtmClient.sendMessageToPeer(mPeerId, message, mChatManager.getSendMessageOptions(), new ResultCallback<Void>() {
@Override
public void onSuccess(Void aVoid) {
...
}

@Override
public void onFailure(ErrorInfo errorInfo) {
...
}
});

Now let’s run our application!

Congratulations! You just built yourself a live streaming application with face filter features and chat messaging!

Source: https://arvrjourney.com/build-a-streaming-application-with-face-filter-on-android-9399a028bb40?source=rss—-d01820283d6d—4

spot_img

Latest Intelligence

spot_img

Chat with us

Hi there! How can I help you?