Google VR NDK Spatial Audio

Google VR contains a powerful spatial audio rendering engine which is optimized for mobile VR. It allows the user to spatialize sound sources in 3D space, including distance and elevation cues. Specifically, the API is capable of playing back spatial sound in two ways:

  • Sound object rendering: This allows the user to create a virtual sound source in 3D space. These sources, while spatialized, are fed with mono audio data.

  • Ambisonic soundfields: Ambisonic recordings are multi-channel audio files which are spatialized all around the listener in 360 degrees. These can be thought of as recorded or pre-baked soundfields. They can be of great use for background effects which sound perfectly spatial. Examples include rain noise, crowd noise or even the sound of the ocean off to one side.

Using the AudioApi

The main entry point for the audio API is the AudioApi class.

Create an AudioApi instance and initialize it at the start of your app. During initialization, a rendering configuration must be specified:

  • GVR_AUDIO_RENDERING_STEREO_PANNING: Stereo panning of all sound objects. This disables HRTF-based rendering.

  • GVR_AUDIO_RENDERING_BINAURAL_LOW_QUALITY: This renders sound objects over a virtual array of 8 loudspeakers arranged in a cube configuration around the listener’s head. HRTF-based rendering is enabled.

  • GVR_AUDIO_RENDERING_BINAURAL_HIGH_QUALITY: This renders sound objects over a virtual array of 16 loudspeakers arranged in an approximate equidistribution about the listener’s head. HRTF-based rendering is enabled.

For most modern mobile phones the high quality mode offers a good balance between performance and audio quality. On Android, initialization also requires a pointer to the JNI environment, the Android application context (note: not the Activity context) and the app's main class loader.

Audio playback on the default audio device can be started and stopped by calling the Pause() and Resume() methods.

Please note that the Update() method must be also called from the main thread at a regular rate. It is used to execute background operations outside of the audio thread.

// Create AudioApi instance.
std::unique_ptr<gvr::AudioApi> audio_api(new gvr::AudioApi);

void Initialize() {
   // Initialize it and start audio engine.
   if (!audio_api->Init(jni_env, android_context, class_loader,
                        GVR_AUDIO_RENDERING_BINAURAL_HIGH_QUALITY)) {
     // Handle failure. Do not proceed in case of failure (calling other
     // controller_api methods without a successful Init will crash with
     // an assert failure.
     return;
   }
}

void OnPause() {
  // Pause audio engine.
   audio_api->Pause();
}

void OnResume() {
  // Resume audio engine.
   audio_api->Resume();
}

void DrawFrame() {
  // Regular call to audio_api from the main thread.
  audio_api->Update();
}

Update listener head position and orientation

To ensure that the audio in your application reacts to user head movement, it is important to update the user’s head orientation in the graphics callback using the head orientation matrix. To obtain the head pose in start space call GvrApi::GetHeadPoseInStartSpace and pass its rotation to GvrAudioApi::SetHeadRotation.

void DrawFrame() {
   gvr::HeadPose head_pose = gvr_api_->GetHeadPoseInStartSpace(target_time);
   gvr_audio_api_->SetHeadRotation(head_pose_.rotation);
}

Spatialization of sound objects

GVR Audio allows the user to create virtual sound objects which can be placed anywhere in space around the listener. To create a new sound object from a preloaded audio sample, use CreateSoundObject(). It returns a handler that can be used to set properties such as the position and the volume of the sound object via SetSoundObjectPosition() and SetSoundVolume(). The spatialized playback of the sound can be triggered with PlaySound() and stopped with StopSound(). IsSoundPlaying() can be used to check if a sound object is currently active.

Note that the sound object handle destroys itself at the moment the sound playback has stopped. This way, no cleanup of sound object handles is needed.

// Preload sound file, create sound handle and start playback.
Static std::string kSoundFile = sound.wav;
gvr_audio_api->PreloadSoundfile(kSoundFile);
AudioSourceId source_id = gvr_audio_api_->CreateSoundObject(kSoundFile);
gvr_audio_api->SetSoundObjectPosition(source_id, position_x, position_y, position_z);
gvr_audio_api->PlaySound(source_id, true /* looped playback */);

Rendering of ambisonic soundfields

The GVR Audio System also provides the user with the ability to play back ambisonic soundfields. Ambisonic soundfields are captured or pre-rendered 360 degree recordings. It is best to think of them as equivalent to 360 degree video. While they envelop and surround the listener, they only react to the listener's rotational movement. That is, one cannot walk towards features in the soundfield. Soundfields are ideal for accompanying 360 degree video playback, for introducing background and environmental effects such as rain or crowd noise, or even for pre baking 3D audio to reduce rendering costs. The GVR Audio System supports full 3D First Order Ambisonic recordings using ACN channel ordering and SN3D normalization.

For more information please see our Spatial Audio specification on Github.

To obtain a soundfield handle, call CreateSoundfield(). It returns a handle that allows the user to begin playback of the soundfield, to alter the soundfield’s volume or to stop soundfield playback and as such destroy the object.

Sound Files and Preloading

Both mono sound files for use with Sound Objects and multi-channel Ambisonic sound files can be preloaded into memory before playback with the PreloadSoundfile() method or alternatively streamed during playback. Preloading can be useful to reduce CPU usage especially if the same audio clip is likely to be played back many times. In this case playback latency is also reduced. To clean-up unused sound files, they can be unloaded with UnloadSoundfile().

Sound playback

Start the playback of a sound.

gvr_audio_api_->PlaySound(source_id, true /* looped playback */);

Check if a sound is playing.

bool is_playing = gvr_audio_api_->IsSoundPlaying(source_id);

Pause the playback of a sound.

gvr_audio_api_->PauseSound(source_id);

Resume the playback of a sound.

gvr_audio_api_->ResumeSound(source_id);

Stop the playback of a sound and destroy the corresponding Sound Object or soundfield.

gvr_audio_api_->StopSound(source_id);

Check that a sourceID corresponds to a valid source which exists and is in a playable state.

bool is_valid gvr_audio_api_->IsSourceIdValid(source_id);

Room effects

GVR Audio provides a powerful reverb engine which can be used to create customized room effects by specifying the size of a room and a material for each surface of the room from the enum AudioMaterialName. Each of these surface materials has unique absorption properties which differ with frequency. The room created will be centered around the listener. Note that the Google VR Audio System uses meters as the unit of distance throughout.

EnableRoom() enables or disables room effects with smooth transitions. SetRoomProperties() allows the user to describe the room based on its dimensions and its surface properties. For example, one can expect very large rooms to be more reverberant than smaller rooms and a room with hard surface materials such as brick to be more reverberant than one with soft absorbent materials such as heavy curtains on every surface.

Note that when a sound source is located outside of the room the listener is in, it will sound different to sources located within the room due to attenuation of both the direct sound and the reverb on that source. Sources located far outside the room the listener is in will not be audible to the listener.