Create a Walkie Talkie App using HUAWEI Nearby Service in Xamarin.Android

Hamdi Aktar
Huawei Developers
Published in
7 min readJan 26, 2021

HUAWEI HMS Nearby Service allows apps to easily discover nearby devices and sets up secure communication channels with them using technologies such as Bluetooth and Wi-Fi without connecting to the Internet and transfers bytes, files, and streams to them; supports seamless nearby interactions, such as multi-player gaming, real-time collaboration, resource broadcasting, and content sharing.

In this article, we’ll create a Xamarin app that enables sending and receiving voice streams between devices using Nearby Service.

Preparations

Before we dive into the details we have a few steps we need to do in order to start developing our app.

Configuring App Information in AppGallery Connect

First of all, we need to be a registered as Huawei Mobile Developer and create an application in Huawei App Console in order to use HMS Nearby Service. After that we need to configure the signing certificate fingerprint to connect with HMS core SDK and finally register for the required services. Follow the link below for more details: Configuration For Nearby Service.

Installing Huawei Nearby Service Package

We need to install Huawei Nearby Service NuGet package in order to use the Nearby SDK. For installation: Open NuGet Package Manager and install Huawei.Hms.Nearby.

Integrating the HMS Core SDK

Before starting with the development, we need to integrate the HMS Core SDK into the Xamarin.Android project. For details, please refer to Integrating the HMS Core SDK.

Setting Package Information in Xamarin

Nearby Service SDK works only in signed apps. Open Xamarin.Android project’s properties Android Package Signing section then fill the areas for both Debug and Release. For details, please refer to Setting Package Information in Xamarin.

Manifest & Permissions

We have to update the application’s manifest file by declaring permissions that we need as shown below.

Request runtime permissions in our app in order to use the Nearby Service. The following code checks whether the user has granted the required permissions in Main Activity. Some of the used methods are called from a helper class called PermissionUtil. The full code can be found on Github

Configuring The Buffer Parameters

We’ll need to use a buffer to write the audio recorded from the microphone and read the audio stream received at the other end as well. Depending on what phone is used we need to set the best buffer size and audio sample rate accordingly.

To do so, we created the AudioBuffer class which will create the buffer with minimum size and best sample rate compatible with the size.

Audio Recorder

We created The AudioRecorder class to handle audio recording functions, when we’re calling the constructor we pass a ParcelFileDescriptor object, which will be used to hold the audio stream data.

When the recording is started, we start a new thread. In this thread we create an AudioRecorder object that manages the audio resources for Android applications to record audio from the audio input hardware. We initiate this object with parameters like the microphone to use, buffer size and sample rate (using AudioBuffer class).

After creating the AudioRecord object successfully, we call StartRecording() method to start recording audio. After that we can read the recorded data from the buffer and write it on the ParcelFileDescriptor object which will be sent as stream to other devices.

After finishing recording we’ll terminate the thread and release the resources.

AudioPlayer

AudioPlayer class will handle playing the audio on the device when the audio stream is received. While creating object from this class we pass an InputStream object which will be the audio to play.

We’ll play each audio received in its own thread. In the thread we create an AudioTrack object that manages and plays a single audio resource for Android applications. We initiate this object with parameters like the type of the stream, encoding, buffer size and sample rate (using AudioBuffer class).

After creating the AudioTrack object successfully, we call Play() method to start playing the audio. In a loop, we’ll read from the audio from the InputStream and call Write() to writes the audio data to the audio sink for playback.

After finishing playing the audio we’ll terminate the thread and release the resources.

Using Nearby SDK

Preparations

We’ll start by declaring some variables and classes that we’ll use in the process of using Nearby SDK

Also, we’ll create a State enum to represent the current status of the device. Device class will represent a discovered device and will hold information like the name of the device and the Endpoint ID

Layout

We’ll have one layout with one big Button that will be used to trigger the audio recording function, also we’ll have a couple Textview widgets to display some information

UI Controls

We want to record audio while the button is clicked, when it is released we’ll stop recording. To achieve this we’ll catch Touch action on the button

Also, we’ll update the Textview contents based on the state of the device

Discovering Other Devices

Nearby Service supports three policies:

  • PolicyMesh: A point-to-point connection policy that supports an M-to-N or cluster-shaped connection topology.
  • PolicyStar: A point-to-point connection policy that supports a 1-to-N or star-shaped connection topology.
  • PolicyP2p:A point-to-point connection policy that supports a 1-to-1 connection topology.

When the app is launched, we’ll enable Broadcasting and scanning features. Broadcasting allows the device to discover other devices where scanning allows the device to be discovered by others. To do so, we’ll create an instance of DiscoveryEngin class that will handle the connection process.

First, we’ll call StartScanning() using the DiscoveryEngin instance we created. We’ll pass the service ID which is a parameter uniquely identifies the app, an instance of ScanOption class that will specify some options like the Policy and lastly an implementation of ScanEndpointCallback class which is a scanning-listening callback class that notifies the discoverer of the scanning result, for example, a device is discovered or lost.

Also, we’ll attach a listener to the task to catch if there is any error happened in the process.

We’ll call StartBroadcasting() in a similar way, we’ll pass an name for our Endpoint which will be used to identify our device when connect with others, the service ID, an instance of BroadcastOption class that will specify some options like the Policy and an implementation of ConnectCallback class which is a connection-listening callback class that notifies us of the connection status.

In the ConnectCallback class we have 3 methods:

OnEstablish: Called back when a connection has been established and both ends need to confirm whether to accept the connection. (In our app we will accept the connection directly)

OnResult: Called back when either end accepts or rejects the connection.

OnDisconnected: Called back when the remote endpoint disconnects or the connection is unreachable.

Going back to OnFound() method we override in ScanEndpointCallback we can see that we call EndpointDiscovered(device) passing the discovered device information, in this method we request a connection with the device passed by calling RequestConnect method. When we are calling this method we pass the name of our endpoint, the endpoint of the device we are trying to connect to and an instance of ConnectCallback class.

On the other side, in OnEstablish() method we called EndpointDiscovered to accept the connection automatically using AcceptConnect method. In this method we pass the endpoint of the device we accepted to connect with and an instance of DataCallback class which will handle the data transmission operations between devices.

In this class we have two methods:

OnReceived: called when data is received.

OnTransferUpdate: Called back to obtain the data sending or receiving status.

In OnReceived method we are checking if the type of the data received is a stream data then we are calling OnReceive method passing the received data to play the Audio stream.

All is left now is, sending the recorded audio to nearby devices. By checking StartRecording method that we used when the microphone button is clicked, we can see that we created ParcelFileDescriptor pipe by calling ParcelFileDescriptor. CreatePipe() then we send the read side of the pipe by calling Send method and pass the write side of the pipe to an AudioRecorder object to start writing the audio data on it.

In the Send method we are using an instance of TransferEngine to send the audio to other devices, we call the SendData method and pass the list of endpoints to receive the stream and the stream data as well.

We should also add some controls over the device states to take actions depending on the state, we create OnStateChanged to be called whenever the state is changed

At this point we covered all the step required to create the app. Of course, there are still some details to be discovered in the full code which can be found on Github.

Thanks for your time reading the article and hope it gave you a good idea about Nearby service. If you have any questions, you can ask in Huawei Developer Forums.

References and useful links

--

--