Closed12

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

EkitoEkito

セッションを作成

空間オーディオ機能を実装するためには、Jetpack XR SDKJetpack 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)
    }
}

https://developer.android.com/develop/xr/jetpack-xr-sdk/add-session?hl=ja

EkitoEkito

空間オーディオが使用可能か確認

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)) {

        }
    }
}

https://developer.android.com/develop/xr/jetpack-xr-sdk/check-spatial-capabilities?hl=ja

EkitoEkito

フルスペースで起動

空間オーディオ機能はフルスペースモードでのみサポートされているので、AndroidManifest.xmlを編集して、フルスペースでの起動をデフォルトとして設定する。

(参考)Android XRにおけるフルスペースとホームスペース

https://developer.android.com/design/ui/xr/guides/foundations?hl=ja

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>

https://developer.android.com/develop/xr/jetpack-xr-sdk/transition-home-space-to-full-space?hl=ja

EkitoEkito

アセットを追加

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

EkitoEkito

音声タイプに応じてAPIを選択

使用する音声タイプに応じて、以下のAPIを使い分ける。

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

https://developer.android.com/develop/xr/jetpack-xr-sdk/add-spatial-audio?hl=ja

EkitoEkito

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
                    )
                }
            }
        }
    }
}
EkitoEkito

エミュレーターでテスト

https://youtu.be/dp08dKTN-Bo
(なぜか白いパネルが表示されるが)効果音の聞こえる方向が変化していることが分かる。

にー兄さんにー兄さん

(なぜか白いパネルが表示されるが)

もしかしたら、フルスペースで表示されるべきUIがSubspaceコンポーザブル内に定義されていないからかも?

EkitoEkito

なるほど!

Subspaceコンポーザブル内に定義

まさにこの方法でやったら白いパネルが消えた!あざます!!

EkitoEkito

Subspace Composableを呼び出す

3Dレイアウトを作成するには、Subspace Composableを使用する必要があった。

(参考)SubspaceやSubspace Composableについて

https://developer.android.com/develop/xr/jetpack-xr-sdk/develop-ui?hl=ja

以下のようにスクリプトを修正したら、白いパネルは消えた。

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日前にクローズされました