Closed12
Android XRの空間オーディオを試す

前提
Android XRの空間オーディオ機能を使うには、現時点ではAndroid StudioでJetpack XR SDKを使用して開発する必要がある模様。ちなみに、Android XRの開発ツールは、他にUnity、OpenXR、WebXRがある。

開発環境セットアップ
以下のサイトに沿って、Android StudioのCanaryビルドをダウンロードして、Android XRプロジェクトを作成する。また、エミュレーターとしてAndroid Virtual Deviceを作成する。

セッションを作成
空間オーディオ機能を実装するためには、Jetpack XR SDKのJetpack SceneCoreライブラリを使用する。Jetpack SceneCoreライブラリは、3Dコンテンツを実装するためのライブラリらしい。
まずMainActivity.kt
を編集して、Session
を作成する。
MainActivity.kt
package com.example.spatialaudiotest
import androidx.activity.ComponentActivity
import android.os.Bundle
import androidx.xr.scenecore.Session
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Sessionの作成
val xrSession = Session.create(this)
}
}

空間オーディオが使用可能か確認
getSpatialCapabilities
を呼び出して、空間オーディオ機能が使用可能か確認する。
MainActivity.kt
package com.example.spatialaudiotest
import androidx.activity.ComponentActivity
import android.os.Bundle
import androidx.xr.scenecore.Session
import androidx.xr.scenecore.SpatialCapabilities
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val xrSession = Session.create(this)
// 空間オーディオが使用可能か確認
if (xrSession.getSpatialCapabilities().hasCapability(
SpatialCapabilities.SPATIAL_CAPABILITY_SPATIAL_AUDIO)) {
}
}
}

フルスペースで起動
空間オーディオ機能はフルスペースモードでのみサポートされているので、AndroidManifest.xml
を編集して、フルスペースでの起動をデフォルトとして設定する。
(参考)Android XRにおけるフルスペースとホームスペース
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.SpatialAudioTest"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.SpatialAudioTest">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
// フルスペースでの起動をデフォルトとする
<property
android:name="android.window.PROPERTY_XR_ACTIVITY_START_MODE"
android:value="XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED" />
</activity>
</application>
</manifest>

アセットを追加
app/src/main
フォルダ下にassets
フォルダを作成し、さらにその下にsounds
フォルダを作成する。このsounds
フォルダに、オーディオファイルを追加する。

音声タイプに応じてAPIを選択
使用する音声タイプに応じて、以下のAPIを使い分ける。
-
SoundPool
: 短い効果音用。事前に読み込まれて繰り返し使用できる。 -
ExoPlayer
: ステレオ音声やサラウンド音声用。 -
MediaPlayer
: アンビソニック音声に対応。 -
AudioTrack
: 音声データの読み込み方法をカスタマイズできる。

SoundPoolで効果音の再生
効果音のオーディオファイルをSoundPoolに読み込み、Entityの位置で再生する。
MainActivity.kt
package com.example.spatialaudiotest
import android.media.AudioAttributes
import android.media.SoundPool
import androidx.activity.ComponentActivity
import android.os.Bundle
import androidx.xr.scenecore.Session
import androidx.xr.scenecore.SpatialCapabilities
import androidx.xr.runtime.math.Pose
import androidx.xr.scenecore.PointSourceAttributes
import androidx.xr.scenecore.SpatialSoundPool
import android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION
import android.media.AudioAttributes.USAGE_ASSISTANCE_SONIFICATION
import androidx.xr.runtime.math.Quaternion
import androidx.xr.runtime.math.Vector3
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val xrSession = Session.create(this)
if (xrSession.getSpatialCapabilities().hasCapability(
SpatialCapabilities.SPATIAL_CAPABILITY_SPATIAL_AUDIO)) {
val maxVolume = 1F
val lowPriority = 0
val infiniteLoop = -1
val normalSpeed = 1F
val soundPool = SoundPool.Builder()
.setAudioAttributes(
AudioAttributes.Builder()
.setContentType(CONTENT_TYPE_SONIFICATION)
.setUsage(USAGE_ASSISTANCE_SONIFICATION)
.build()
)
.build()
// 音声を再生するEntityを作成
val entity = xrSession.createEntity("", Pose(Vector3(0f, 0f, -2f), Quaternion.Identity))
val pointSource = PointSourceAttributes(entity)
val appContext = this
val soundEffect = appContext.assets.openFd("sounds/se.mp3")
val pointSoundId = soundPool.load(soundEffect, lowPriority)
soundPool.setOnLoadCompleteListener { soundPool, sampleId, status ->
// オーディオファイルがSoundPoolに読み込まれた後に音声を再生
if (status == 0) {
SpatialSoundPool.play(
session = xrSession,
soundPool = soundPool,
soundID = pointSoundId,
attributes = pointSource,
volume = maxVolume,
priority = lowPriority,
loop = infiniteLoop,
rate = normalSpeed
)
}
}
}
}
}

エミュレーターでテスト
(なぜか白いパネルが表示されるが)効果音の聞こえる方向が変化していることが分かる。

Subspace Composableを呼び出す
3Dレイアウトを作成するには、Subspace Composableを使用する必要があった。
(参考)SubspaceやSubspace Composableについて
以下のようにスクリプトを修正したら、白いパネルは消えた。
MainActivity.kt
package com.example.spatialaudiotest
import android.media.AudioAttributes
import android.media.SoundPool
import androidx.activity.ComponentActivity
import android.os.Bundle
import androidx.xr.scenecore.Session
import androidx.xr.scenecore.SpatialCapabilities
import androidx.xr.runtime.math.Pose
import androidx.xr.scenecore.PointSourceAttributes
import androidx.xr.scenecore.SpatialSoundPool
import android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION
import android.media.AudioAttributes.USAGE_ASSISTANCE_SONIFICATION
import androidx.xr.runtime.math.Quaternion
import androidx.xr.runtime.math.Vector3
import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable
import androidx.xr.compose.spatial.Subspace
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Subspace {
MyComposable() // Subspace Composable
}
}
}
@Composable
private fun MyComposable() {
// Sessionの作成
val xrSession = Session.create(this)
// 空間オーディオが使用可能か確認
if (xrSession.getSpatialCapabilities().hasCapability(
SpatialCapabilities.SPATIAL_CAPABILITY_SPATIAL_AUDIO)) {
val maxVolume = 1F
val lowPriority = 0
val infiniteLoop = -1
val normalSpeed = 1F
val soundPool = SoundPool.Builder()
.setAudioAttributes(
AudioAttributes.Builder()
.setContentType(CONTENT_TYPE_SONIFICATION)
.setUsage(USAGE_ASSISTANCE_SONIFICATION)
.build()
)
.build()
// 音声の再生位置を設定
val entity = xrSession.createEntity("", Pose(Vector3(0f, 0f, -1f), Quaternion.Identity))
val pointSource = PointSourceAttributes(entity)
val appContext = this
val soundEffect = appContext.assets.openFd("sounds/se.mp3")
val pointSoundId = soundPool.load(soundEffect, lowPriority)
soundPool.setOnLoadCompleteListener { soundPool, sampleId, status ->
// オーディオファイルがSoundPoolに読み込まれた後に音声を再生
if (status == 0) {if(status == 0){
SpatialSoundPool.play(
session = xrSession,
soundPool = soundPool,
soundID = pointSoundId,
attributes = pointSource,
volume = maxVolume,
priority = lowPriority,
loop = infiniteLoop,
rate = normalSpeed
)
}
}
}
}
}
このスクラップは26日前にクローズされました