Agora provides spatial audio effects to give users an immersive audio experience in scenarios such as esports competitions and online conferences.
Agora provides users local Cartesian coordinate system calculation solution:
This solution uses the ILocalSpatialAudioEngine
class to implement spatial audio effects and calculates the relative positions of the local and remote users through the SDK. You need to call updateSelfPosition
and updateRemotePosition
to update the spatial coordinates of the local and remote users respectively, so that the local user can hear the remote user's spatial audio effects.
Agora provides media player with local Cartesian coordinate system calculation solution:
This solution calculates the relative positions of the local user and the media player through the SDK. You need to call updateSelfPosition
and updatePlayerPositionInfo
in the ILocalSpatialAudioEngine
class to update the spatial coordinates of the local user and the media player, respectively, so that the local user can hear the spatial audio effect of the media player.
This solution implements spatial audio effects through the ILocalSpatialAudioEngine
class. The API call sequence and operation steps are as follows:
Before calling other Agora APIs, call create
of the RtcEngine
class, and fill in your App ID to initialize the RtcEngine
object.
Before calling other APIs of the ILocalSpatialAudioEngine
class, call create
and initialize
of the ILocalSpatialAudioEngine
class to initialize the ILocalSpatialAudioEngine
object.
Call setAudioProfile
to set the profile
to AUDIO_PROFILE_DEFAULT
and the scenario
to AUDIO_SCENARIO_GAME_STREAMING
.
Call joinChannel
with the options
parameter to join the channel (using RTC Token). Set channelProfile
to CHANNEL_PROFILE_LIVE_BROADCASTING
and clientRoleType
to CLIENT_ROLE_BROADCASTER
in ChannelMediaOptions
.
Agora subscribes to the audio streams of all remote users by default. You need to call muteAllRemoteAudioStreams(true)
of RtcEngine
to unsubscribe all remote users; otherwise, the audio reception range you set in step 6 is invalid.
Call the following method to set the audio reception range:
setMaxAudioRecvCount
to set the maximum number of audio streams that a user can receive in a specified audio reception range.setAudioRecvRange
to set the audio reception range (meters) of the local user.setDistanceUnit
to set the length (meters) of the game engine unit distance.You need to call updateSelfPosition
and updateRemotePosition
to update the spatial coordinates of the local and the remote users successively, so that the local user can hear the remote user's spatial audio effects.
If you do not need to experience spatial audio effects, call clearRemotePositions
to delete the spatial position information of all remote users. After the deletion, the local user cannot hear all remote users.
Call destroy
of ILocalSpatialAudioEngine
to destroy the ILocalSpatialAudioEngine
object.
ILocalSpatialAudioEngine
object must be destroyed before calling destroy
of the RtcEngine
.Call leaveChannel
and destroy
of the RtcEngine
class to leave the channel and destroy the RtcEngine
object.
This solution implements spatial audio effects through the updateSelfPosition
and updatePlayerPositionInfo
in the ILocalSpatialAudioEngine
class. Taking the ILocalSpatialAudioEngine
class as an example, the operation steps are as follows:
Before calling other Agora APIs, call create
of the RtcEngine
class, and fill in your App ID to initialize the RtcEngine
object.
Before calling other APIs of the ILocalSpatialAudioEngine
class, call create
and initialize
of the ILocalSpatialAudioEngine
class to initialize the ILocalSpatialAudioEngine
object.
Call setAudioProfile
to set the profile
to AUDIO_PROFILE_DEFAULT
and the scenario
to AUDIO_SCENARIO_GAME_STREAMING
.
Call joinChannel
with the options
parameter to join the channel (using RTC Token). Set channelProfile
to CHANNEL_PROFILE_LIVE_BROADCASTING
and clientRoleType
to CLIENT_ROLE_BROADCASTER
in ChannelMediaOptions
.
Agora subscribes to the audio streams of all remote users by default. You need to call muteAllRemoteAudioStreams(true)
of RtcEngine
to unsubscribe all remote users; otherwise, the audio reception range you set in step 6 is invalid.
Call the following method to set the audio reception range:
setMaxAudioRecvCount
to set the maximum number of audio streams that a user can receive in a specified audio reception range.setAudioRecvRange
to set the audio reception range (meters) of the local user.setDistanceUnit
to set the length (meters) of the game engine unit distance.You need to call updateSelfPosition
and updateRemotePosition
to update the spatial coordinates of the local and the remote users successively, so that the local user can hear the remote user's spatial audio effects.
Call destroy
of ILocalSpatialAudioEngine
to destroy the ILocalSpatialAudioEngine
object.
ILocalSpatialAudioEngine
object must be destroyed before calling destroy
of the RtcEngine
.Call destroy
of the RtcEngine
to destroy the RtcEngine
object.
This section shows sample code that uses the local Cartesian coordinate system calculation solution to implement the spatial audio effects for the media player:
import static io.agora.api.example.common.model.Examples.ADVANCED;
import static io.agora.mediaplayer.Constants.MediaPlayerState.PLAYER_STATE_IDLE;
import static io.agora.mediaplayer.Constants.MediaPlayerState.PLAYER_STATE_OPEN_COMPLETED;
import static io.agora.mediaplayer.Constants.MediaPlayerState.PLAYER_STATE_PLAYBACK_COMPLETED;
import static io.agora.mediaplayer.Constants.MediaPlayerState.PLAYER_STATE_STOPPED;
import io.agora.api.example.common.Constant;
import io.agora.mediaplayer.Constants;
import io.agora.mediaplayer.IMediaPlayer;
import io.agora.mediaplayer.IMediaPlayerObserver;
import io.agora.mediaplayer.data.PlayerUpdatedInfo;
import io.agora.mediaplayer.data.SrcInfo;
import io.agora.rtc2.IRtcEngineEventHandler;
import io.agora.rtc2.RtcEngine;
import io.agora.rtc2.SpatialAudioParams;
import io.agora.spatialaudio.ILocalSpatialAudioEngine;
import io.agora.spatialaudio.LocalSpatialAudioConfig;
import io.agora.spatialaudio.RemoteVoicePositionInfo;
public class SpatialSound extends BaseFragment {
private RtcEngine engine;
private IMediaPlayer mediaPlayer;
private ILocalSpatialAudioEngine localSpatial;
private RemoteVoicePositionInfo positionInfo = new RemoteVoicePositionInfo();
private int speakerUid;
@Nullable
@Override
private void startRecord() {
startTv.setVisibility(View.GONE);
// Opens a media resource
mediaPlayer.open(Constant.URL_PLAY_AUDIO_FILES, 0);
LocalSpatialAudioConfig localSpatialAudioConfig = new LocalSpatialAudioConfig();
localSpatialAudioConfig.mRtcEngine = engine;
// Creates an ILocalSpatialAudioEngine
localSpatial = ILocalSpatialAudioEngine.create();
// Initializes the ILocalSpatialAudioEngine
localSpatial.initialize(localSpatialAudioConfig);
// Sets the audio reception range (meters)
localSpatial.setAudioRecvRange(50);
// Sets the length (in meters) of the game engine distance per unit.
localSpatial.setDistanceUnit(1);
// Updates the spatial position of the local user.
float[] pos = new float[]{0.0F, 0.0F, 0.0F};
float[] forward = new float[]{1.0F, 0.0F, 0.0F};
float[] right = new float[]{0.0F, 1.0F, 0.0F};
float[] up = new float[]{0.0F, 0.0F, 1.0F};
localSpatial.updateSelfPosition(pos, forward, right, up);
}
private void updateSpatialSoundParam() {
float transX = speakerIv.getTranslationX();
float transY = speakerIv.getTranslationY();
double viewDistance = Math.sqrt(Math.pow(transX, 2) + Math.pow(transY, 2));
double viewMaxDistance = Math.sqrt(Math.pow((rootView.getWidth() - speakerIv.getWidth()) / 2.0f, 2) + Math.pow((rootView.getHeight() - speakerIv.getHeight()) / 2.0f, 2));
double spkMaxDistance = 3;
double spkMinDistance = 1;
double spkDistance = spkMaxDistance * (viewDistance / viewMaxDistance);
if (spkDistance < spkMinDistance) {
spkDistance = spkMinDistance;
}
if (spkDistance > spkMaxDistance) {
spkDistance = spkMaxDistance;
}
double degree = getDegree((int) transX, (int) transY);
if (transX > 0) {
degree = 360 - degree;
}
double posForward = spkDistance * Math.cos(degree);
double posRight = spkDistance * Math.sin(degree);
// Updates the spatial position of the media player.
RemoteVoicePositionInfo positionInfo = new RemoteVoicePositionInfo();
positionInfo.forward = new float[]{1.0F, 0.0F, 0.0F};
positionInfo.position = new float[]{(float) posForward, (float) posRight, 0.0F};
localSpatial.updatePlayerPositionInfo(mediaPlayer.getMediaPlayerId(), positionInfo);
}
private int getDegree(int point1X, int point1Y) {
int vertexPointX = 0, vertexPointY = 0, point0X = 0;
int point0Y = -10;
int vector = (point0X - vertexPointX) * (point1X - vertexPointX) + (point0Y - vertexPointY) * (point1Y - vertexPointY);
double sqrt = Math.sqrt(
(Math.abs((point0X - vertexPointX) * (point0X - vertexPointX)) + Math.abs((point0Y - vertexPointY) * (point0Y - vertexPointY)))
* (Math.abs((point1X - vertexPointX) * (point1X - vertexPointX)) + Math.abs((point1Y - vertexPointY) * (point1Y - vertexPointY)))
);
double radian = Math.acos(vector / sqrt);
return (int) (180 * radian / Math.PI);
}
@Override
public void onDestroy() {
super.onDestroy();
// Stops playing the media resource
mediaPlayer.stop();
handler.removeCallbacksAndMessages(null);
handler.post(RtcEngine::destroy);
engine = null;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Context context = getContext();
if (channel != null) {
return;
}
try {
String appId = getString(R.string.agora_app_id);
// Creates and initializes an RtcEngine
engine = RtcEngine.create(getContext().getApplicationContext(), appId, iRtcEngineEventHandler);
// Creates an IMediaPlayer
mediaPlayer = engine.createMediaPlayer();
// Registers a media player observer.
mediaPlayer.registerPlayerObserver(iMediaPlayerObserver);
} catch (Exception e) {
e.printStackTrace();
getActivity().onBackPressed();
}
}
private IMediaPlayerObserver iMediaPlayerObserver = new IMediaPlayerObserver() {
@Override
// Reports the playback state change.
public void onPlayerStateChanged(io.agora.mediaplayer.Constants.MediaPlayerState mediaPlayerState, io.agora.mediaplayer.Constants.MediaPlayerError mediaPlayerError) {
Log.e(TAG, "onPlayerStateChanged mediaPlayerState " + mediaPlayerState);
if (mediaPlayerState.equals(PLAYER_STATE_OPEN_COMPLETED)) {
// Sets the loop playback of media resources
mediaPlayer.setLoopCount(-1);
// Stops playing the media resource
mediaPlayer.play();
}
}
};
}
Agora provides an open source sample project called SpatialSound on GitHub. You can download this sample project to try it out or refer to the source code.