mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-19 21:37:57 +08:00
368 lines
12 KiB
Python
368 lines
12 KiB
Python
# Released under the MIT License. See LICENSE for details.
|
|
#
|
|
"""Functionality to build the openal library."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import subprocess
|
|
from typing import TYPE_CHECKING
|
|
|
|
from efro.error import CleanError
|
|
|
|
if TYPE_CHECKING:
|
|
pass
|
|
|
|
# Arch names we take and their official android versions.
|
|
ARCHS = {
|
|
'arm': 'armeabi-v7a',
|
|
'arm64': 'arm64-v8a',
|
|
'x86': 'x86',
|
|
'x86_64': 'x86_64',
|
|
}
|
|
|
|
MODES = {'debug', 'release'}
|
|
|
|
|
|
def _build_dir(arch: str, mode: str) -> str:
|
|
"""Build dir given an arch and mode."""
|
|
return f'build/openal_build_android_{arch}_{mode}'
|
|
|
|
|
|
def build_openal(arch: str, mode: str) -> None:
|
|
"""Do the thing."""
|
|
# pylint: disable=too-many-statements
|
|
# pylint: disable=too-many-locals
|
|
from efrotools.util import replace_exact
|
|
|
|
if arch not in ARCHS:
|
|
raise CleanError(f"Invalid arch '{arch}'.")
|
|
|
|
if mode not in MODES:
|
|
raise CleanError(f"Invalid mode '{mode}'.")
|
|
|
|
# If true, we suppress most OpenAL logs to keep logcat tidier.
|
|
reduce_logs = False
|
|
|
|
# Inject a function to reroute OpenAL logs to ourself.
|
|
reroute_logs = True
|
|
|
|
# Inject an env var to force Oboe to use OpenSL backend.
|
|
opensl_fallback_option = True
|
|
|
|
# set_game_usage = True
|
|
|
|
# Get ndk path.
|
|
ndk_path = (
|
|
subprocess.run(
|
|
['tools/pcommand', 'android_sdk_utils', 'get-ndk-path'],
|
|
check=True,
|
|
capture_output=True,
|
|
)
|
|
.stdout.decode()
|
|
.strip()
|
|
)
|
|
|
|
# Grab OpenALSoft
|
|
builddir = _build_dir(arch, mode)
|
|
subprocess.run(['rm', '-rf', builddir], check=True)
|
|
subprocess.run(['mkdir', '-p', os.path.dirname(builddir)], check=True)
|
|
subprocess.run(
|
|
['git', 'clone', 'https://github.com/kcat/openal-soft.git', builddir],
|
|
check=True,
|
|
)
|
|
subprocess.run(
|
|
[
|
|
'git',
|
|
'checkout',
|
|
# '1.23.1',
|
|
# '1381a951bea78c67281a2e844e6db1dedbd5ed7c',
|
|
# 'bc83c874ff15b29fdab9b6c0bf40b268345b3026',
|
|
'59c466077fd6f16af64afcc6260bb61aa4e632dc',
|
|
],
|
|
check=True,
|
|
cwd=builddir,
|
|
)
|
|
|
|
# Grab Oboe
|
|
builddir_oboe = f'{builddir}_oboe'
|
|
subprocess.run(['rm', '-rf', builddir_oboe], check=True)
|
|
subprocess.run(['mkdir', '-p', os.path.dirname(builddir_oboe)], check=True)
|
|
subprocess.run(
|
|
[
|
|
'git',
|
|
'clone',
|
|
'https://github.com/google/oboe',
|
|
builddir_oboe,
|
|
],
|
|
check=True,
|
|
)
|
|
subprocess.run(['git', 'checkout', '1.8.0'], check=True, cwd=builddir_oboe)
|
|
|
|
if bool(True):
|
|
oboepath = f'{builddir}/alc/backends/oboe.cpp'
|
|
with open(oboepath, encoding='utf-8') as infile:
|
|
txt = infile.read()
|
|
|
|
# Also disable opening a stream just to test that it works.
|
|
txt = replace_exact(
|
|
txt,
|
|
(
|
|
' /* Open a basic output stream, just to ensure'
|
|
' it can work. */\n'
|
|
' oboe::ManagedStream stream;\n'
|
|
' oboe::Result result{oboe::AudioStreamBuilder{}'
|
|
'.setDirection(oboe::Direction::Output)\n'
|
|
' ->setPerformanceMode(oboe::PerformanceMode::'
|
|
'LowLatency)\n'
|
|
' ->openManagedStream(stream)};\n'
|
|
' if(result != oboe::Result::OK)\n'
|
|
' throw al::backend_exception{al::backend_error::'
|
|
'DeviceError, "Failed to create stream: %s",\n'
|
|
' oboe::convertToText(result)};\n'
|
|
),
|
|
(
|
|
' /* Open a basic output stream, just to ensure'
|
|
' it can work. */\n'
|
|
' // DISABLED BY ERICF\n'
|
|
' // oboe::ManagedStream stream;\n'
|
|
' // oboe::Result result{oboe::AudioStreamBuilder{}'
|
|
'.setDirection(oboe::Direction::Output)\n'
|
|
' // ->setPerformanceMode(oboe::PerformanceMode::'
|
|
'LowLatency)\n'
|
|
' // ->openManagedStream(stream)};\n'
|
|
' // if(result != oboe::Result::OK)\n'
|
|
' // throw al::backend_exception{al::backend_error::'
|
|
'DeviceError, "Failed to create stream: %s",\n'
|
|
' // oboe::convertToText(result)};\n'
|
|
),
|
|
)
|
|
# Add our fallback option.
|
|
if opensl_fallback_option:
|
|
txt = replace_exact(
|
|
txt,
|
|
(
|
|
' builder.setPerformanceMode('
|
|
'oboe::PerformanceMode::LowLatency);\n'
|
|
),
|
|
(
|
|
' builder.setPerformanceMode('
|
|
'oboe::PerformanceMode::LowLatency);\n'
|
|
' if (getenv("BA_OBOE_USE_OPENSLES")) {\n'
|
|
' TRACE("BA_OBOE_USE_OPENSLES set;'
|
|
' Using OpenSLES\\n");\n'
|
|
' builder.setAudioApi(oboe::AudioApi::OpenSLES);\n'
|
|
' }\n'
|
|
),
|
|
)
|
|
# Set game mode.
|
|
# if set_game_usage:
|
|
# txt = replace_exact(
|
|
# txt,
|
|
# (
|
|
# ' builder.setPerformanceMode('
|
|
# 'oboe::PerformanceMode::LowLatency);\n'
|
|
# ),
|
|
# (
|
|
# ' builder.setPerformanceMode('
|
|
# 'oboe::PerformanceMode::LowLatency);\n'
|
|
# ' builder.setUsage('
|
|
# 'oboe::Usage::Game);\n'
|
|
# ),
|
|
# )
|
|
|
|
with open(oboepath, 'w', encoding='utf-8') as outfile:
|
|
outfile.write(txt)
|
|
|
|
# By default, openalsoft sends all sorts of log messages to the
|
|
# android log. This is reasonable since its possible to filter by
|
|
# tag/level. However I'd prefer it to send only the ones that it
|
|
# would send to stderr so I don't always have to worry about
|
|
# filtering.
|
|
if reduce_logs or reroute_logs:
|
|
loggingpath = f'{builddir}/core/logging.cpp'
|
|
with open(loggingpath, encoding='utf-8') as infile:
|
|
txt = infile.read()
|
|
|
|
logcall = (
|
|
'__android_log_print(android_severity(level),'
|
|
' "openal", "%s", str);'
|
|
)
|
|
condition = 'gLogLevel >= level' if reduce_logs else 'true'
|
|
if reroute_logs:
|
|
logcall = (
|
|
'if (alcCustomAndroidLogger) {\n'
|
|
' alcCustomAndroidLogger(android_severity(level), str);\n'
|
|
'} else {\n'
|
|
f' {logcall}\n'
|
|
'}'
|
|
)
|
|
txt = replace_exact(
|
|
txt,
|
|
(
|
|
' __android_log_print(android_severity(level),'
|
|
' "openal", "%s", str);'
|
|
),
|
|
(
|
|
' // ericf tweak; only send logs that meet some condition.\n'
|
|
f' if ({condition}) {{\n'
|
|
f' {logcall}\n'
|
|
' }'
|
|
),
|
|
)
|
|
|
|
# Note to self: looks like there's actually already a log
|
|
# redirect callback function in OpenALSoft, but it's not an
|
|
# official extension yet and I haven't been able to get it
|
|
# working in its current form. Ideally should adopt that
|
|
# eventually though.
|
|
if reroute_logs:
|
|
txt = replace_exact(
|
|
txt,
|
|
'namespace {\n',
|
|
(
|
|
'extern void (*alcCustomAndroidLogger)(int, const char*);\n'
|
|
'\n'
|
|
'namespace {\n'
|
|
),
|
|
)
|
|
with open(loggingpath, 'w', encoding='utf-8') as outfile:
|
|
outfile.write(txt)
|
|
|
|
# Add a function to set a logging function so we can capture OpenAL
|
|
# logging.
|
|
fpath = f'{builddir}/alc/alc.cpp'
|
|
with open(fpath, encoding='utf-8') as infile:
|
|
txt = infile.read()
|
|
txt = replace_exact(
|
|
txt,
|
|
'ALC_API ALCenum ALC_APIENTRY'
|
|
' alcGetError(ALCdevice *device) noexcept\n',
|
|
(
|
|
'void (*alcCustomAndroidLogger)(int, const char*) = nullptr;\n'
|
|
'\n'
|
|
'ALC_API void ALC_APIENTRY'
|
|
' alcSetCustomAndroidLogger(void (*fn)(int, const char*)) {\n'
|
|
' alcCustomAndroidLogger = fn;\n'
|
|
'}\n'
|
|
'\n'
|
|
'ALC_API ALCenum ALC_APIENTRY'
|
|
' alcGetError(ALCdevice *device) noexcept\n'
|
|
),
|
|
)
|
|
with open(fpath, 'w', encoding='utf-8') as outfile:
|
|
outfile.write(txt)
|
|
|
|
fpath = f'{builddir}/include/AL/alc.h'
|
|
with open(fpath, encoding='utf-8') as infile:
|
|
txt = infile.read()
|
|
txt = replace_exact(
|
|
txt,
|
|
(
|
|
'ALC_API ALCenum ALC_APIENTRY'
|
|
' alcGetError(ALCdevice *device) ALC_API_NOEXCEPT;\n'
|
|
),
|
|
(
|
|
'ALC_API ALCenum ALC_APIENTRY'
|
|
' alcGetError(ALCdevice *device) ALC_API_NOEXCEPT;\n'
|
|
'ALC_API void ALC_APIENTRY alcSetCustomAndroidLogger('
|
|
'void (*fn)(int, const char*));\n'
|
|
),
|
|
)
|
|
with open(fpath, 'w', encoding='utf-8') as outfile:
|
|
outfile.write(txt)
|
|
|
|
# Let's modify the try/catch around api calls so that we can catch
|
|
# and inspect exceptions thrown by them instead of it just resulting
|
|
# in an insta-terminate.
|
|
fpath = f'{builddir}/core/except.h'
|
|
with open(fpath, encoding='utf-8') as infile:
|
|
txt = infile.read()
|
|
txt = replace_exact(
|
|
txt,
|
|
'#define END_API_FUNC catch(...) { std::terminate(); }\n',
|
|
'#define END_API_FUNC\n',
|
|
)
|
|
txt = replace_exact(
|
|
txt, '#define START_API_FUNC try\n', '#define START_API_FUNC\n'
|
|
)
|
|
with open(fpath, 'w', encoding='utf-8') as outfile:
|
|
outfile.write(txt)
|
|
|
|
android_platform = 23
|
|
|
|
subprocess.run(
|
|
[
|
|
'cmake',
|
|
'.',
|
|
f'-DANDROID_ABI={ARCHS[arch]}',
|
|
f'-DCMAKE_BUILD_TYPE={mode}',
|
|
'-DCMAKE_TOOLCHAIN_FILE='
|
|
f'{ndk_path}/build/cmake/android.toolchain.cmake',
|
|
f'-DANDROID_PLATFORM={android_platform}',
|
|
],
|
|
cwd=builddir_oboe,
|
|
check=True,
|
|
)
|
|
subprocess.run(['make'], cwd=builddir_oboe, check=True)
|
|
|
|
subprocess.run(
|
|
[
|
|
'cmake',
|
|
'.',
|
|
f'-DANDROID_ABI={ARCHS[arch]}',
|
|
'-DALSOFT_INSTALL=0', # Prevents odd error.
|
|
'-DALSOFT_REQUIRE_OBOE=1',
|
|
'-DALSOFT_BACKEND_OPENSL=0',
|
|
'-DALSOFT_BACKEND_WAVE=0',
|
|
f'-DCMAKE_BUILD_TYPE={mode}',
|
|
'-DLIBTYPE=STATIC',
|
|
'-DCMAKE_TOOLCHAIN_FILE='
|
|
f'{ndk_path}/build/cmake/android.toolchain.cmake',
|
|
f'-DOBOE_SOURCE={os.path.abspath(builddir_oboe)}',
|
|
f'-DANDROID_PLATFORM={android_platform}',
|
|
],
|
|
cwd=builddir,
|
|
check=True,
|
|
)
|
|
subprocess.run(['make'], cwd=builddir, check=True)
|
|
|
|
print('SUCCESS!')
|
|
|
|
|
|
def gather() -> None:
|
|
"""Gather the things. Assumes all have been built."""
|
|
|
|
# Sanity-check; make sure everything appears to be built.
|
|
for arch in ARCHS:
|
|
for mode in MODES:
|
|
builddir = _build_dir(arch, mode)
|
|
libfile = os.path.join(builddir, 'libopenal.a')
|
|
if not os.path.exists(libfile):
|
|
raise CleanError(f"Built lib not found: '{libfile}'.")
|
|
|
|
outdir = 'src/external/openal-android'
|
|
subprocess.run(['rm', '-rf', outdir], check=True)
|
|
|
|
subprocess.run(['mkdir', '-p', f'{outdir}/include'], check=True)
|
|
|
|
builddir = _build_dir('arm', 'debug') # Doesn't matter here.
|
|
subprocess.run(
|
|
['cp', '-r', f'{builddir}/include/AL', f'{outdir}/include'],
|
|
check=True,
|
|
)
|
|
|
|
for arch, andrarch in ARCHS.items():
|
|
for mode in MODES:
|
|
builddir = _build_dir(arch, mode)
|
|
builddir_oboe = f'{builddir}_oboe'
|
|
installdir = f'{outdir}/lib/{andrarch}_{mode}'
|
|
subprocess.run(['mkdir', '-p', installdir], check=True)
|
|
subprocess.run(
|
|
['cp', f'{builddir}/libopenal.a', installdir], check=True
|
|
)
|
|
subprocess.run(
|
|
['cp', f'{builddir_oboe}/liboboe.a', installdir], check=True
|
|
)
|
|
print('OpenAL gather successful!')
|