Source code for efrotools.openalbuild

# 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}'


[docs] 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!')
[docs] 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!')