前言
如标题, 从之前文章对 axmol 的介绍已经了解到,axmol是基于Cocos2d-x-4.0的持续维护分支,axmol的AudioEngine模块API标准继承了Cocos2d-x-3.x/4.0,但相对于官方最大区别是统一了全平台的音频后端API,全平台基于OpenAL,接下来,本文将从Cocos2d-x音频库的历史到axmol的音频库,来介绍axmol是如何把iOS平台的OpenAL换成openal-soft: https://github.com/kcat/openal-soft
openal-soft的作者github id是kcat
, 以下内容使用kcat
代替openal-soft作者
Cocos2d-x引擎音频库发展史
Cocos2d-x-2.x时代
第1代音频引擎: SimpleAudioEngine,不用介绍,使用过的人都知道,是从cocos2d objc版本继承过来的,除了linux和apple平台是OpenAL, 其他平台底层是调用各操作系统API实现
Cocos2d-x-3.x时代
第2代音频引擎: AudioEngine,linux平台基于fmod, android平台官方基于OpenSL实现了一套,其他平台基于OpenAL接口实现,第二代音频引擎带来了更加易用的接口以及单独给每个声音设置音量的支持,这也是笔者为什么将公司cocos2d-x-2.x时代的SimpleAudioEngine升级为3.x的原因,并且升级过程中也进行了如下优化及问题修复
- 延迟调用声音播放完成回调
- 更准确地OpenAL播放结束判断
- 修复闹钟造成游戏卡死问题: https://github.com/simdsoft/x-studio/commit/75e6c0c
- android平台统一替换为OpenAL,解决频繁切换背景音乐卡死问题
axmol 时代
应该说是2.5代音频引擎,因为继承了Cocos2d-x-3.x的音频接口,axmol对AudioEngine实现做了进一步的统一,linux,android,apple,win32均基于OpenAL接口规范实现,apple平台默认基于系统OpenAL.framework,其他平台基于kcat大神维护的openal-soft版本,音频格式单通道/双通道所有平台均支持的格式:
- mp3
- wav (axmol 新增)
- ogg (axmol iOS平台新增)
axmol 音频库全平台基于openal-soft
这里提下,u0u0维护的cocos2d Lua社区版本也是全平台基于openal-soft的,但接口和3.x/4.x完全不同
- 起源: 苹果公司在iOS 12将OpenAL.framework编辑为废弃框架,不出意外将来会和OpenGL ES被Metal替代一样被干掉,因此负责任的cocos系列引擎都应该提前预备好替代方案,就像cocos官方引擎大神提前部署好Metal渲染后端并发布Cocos2d-x-4.0一样
- 为axmol选择替代方案,最开始我找了除了openal-soft之外,3个跨平台音频后端miniaudio, soloud, mojoal, 为什么其他的呢,主要是因为openal-soft是LGPL-2.1开源协议。同时笔者也尝试了mojoal,另一个OpenAL接口实现,这货替换很方便,但有个缺点,是依赖了SDL2,并且SDL2在Android平台集成还需要引入其相应的java代码,这就不太适合了,后来经过和
kcat
漫长的讨论,作者给了肯定的答案,LGPL-2.1可以以动态链接库的形式在嵌入式的iOS设备免费使用,详见: kcat/openal-soft#187 - 既然确定了,iOS也可以使用openal-soft,那么接下来就是如何能简单地将openal-soft编译为iOS的Dynamic Framework Bundle形式(Tips: 据说早期苹果是不允许iOS使用任何第三方动态链接库的,即使是framework动态链接库也不行,后来苹果出了swift语言,为了swift就引入了Embedded Framework)
- 本着奉献的精神,笔者给openal-soft作者提了支持将其编译为iOS dynamic framework的PR: kcat/openal-soft#466
openal-soft iOS动态Framework支持PR合并
以上提到的PR经历了笔者和kcat
漫长的讨论,大约10天后,最终完成并被合并至openal-soft的master
,经过这次PR的工作,笔者和kcat
至少搞清楚了如下可能是困扰部分程序员
多时的问题:
- cmake-3.18.2以下版本,必须有如下设置,
# The workaround for solve try_compile failed with code sign
# since cmake-3.18.2, not required
# everyting for cmake toolchain config before project(xxx) is better
set(CMAKE_TRY_COMPILE_PLATFORM_VARIABLES
"CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED"
"CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED NO)
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED NO)
否则,用cmake生成iOS arm的xcode工程时try_compile
必然失败,进而导致生成xcode工程失败,而github上第三方 ios.toolchain.cmake默认将ENABLE_STRICT_TRY_COMPILE
设置为了FALSE
,也就是禁用了严格try_compile
,这样做只能解决生成静态arm库的xcode工程问题,而生成动态arm库的xcode工程时会导致错误的检查到平台本身没有的系统库,从而添加链接选项,最终导致动态库构建的过程中链接失败
- 对于生成ios的xcode工程如果包含armv7,必须设置CMAKE_OSX_DEPLOYMENT_TARGET且必须<
11.0
, 否则默认DEPLOMENT_TARGET会是mac系统安装的最新版本,如iOS13
,从而导致xcode clang编译失败
# Fix compile failed with armv7 deployment target >= 11.0, xcode clang will report follow error
# clang: error: invalid iOS deployment version '--target=armv7-apple-ios13.6',
# iOS 10 is the maximum deployment target for 32-bit targets
# If not defined CMAKE_OSX_DEPLOYMENT_TARGET, cmake will choose latest deployment target
if(APPLE AND CMAKE_SYSTEM_NAME STREQUAL "iOS")
if("${CMAKE_OSX_ARCHITECTURES}" MATCHES ".*armv7.*")
if(NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET
OR "${CMAKE_OSX_DEPLOYMENT_TARGET}" VERSION_GREATER "11.0"
OR "${CMAKE_OSX_DEPLOYMENT_TARGET}" VERSION_EQUAL "11.0")
message(STATUS "Sets iOS minimum deployment target to 10.0 for armv7")
# a. armv7 maximum deployment 10.x
# b. armv7 TLS require deployment 10.x
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.0" CACHE STRING "Minimum OS X deployment version")
endif()
endif()
endif()
- 经过以上修改,能成功编译出openal-soft的动态framework,然而事情没有结束,当笔者将编译好的
soft_oal.framework
链接到axmol的cpp-tests并部署到iphone手机上启动候立刻闪退,闪退原因是dyld error: /Users/.../soft_oal.framework/soft_oal
加载不到动态库,明眼一看,竟然尝试去加载macbook本机的编译生成路径,经过一番搜索,笔者发现有个@rpath
的东西需要启用,大概意思就是将生成的动态framework里的路径用@rpath
代替,贴出CMake脚本
# Build: Fix rpath in iOS shared libraries
# If this flag is not set, the final dylib binary ld path will be
# /User/xxx/*/soft_oal.framework/soft_oal, which can't be loaded by iOS devices.
# See also: https://github.com/libjpeg-turbo/libjpeg-turbo/commit/c80ddef7a4ce21ace9e3ca0fd190d320cc8cdaeb
if(NOT CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG)
set(CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG "-Wl,-rpath,")
endif()
set_target_properties(${IMPL_TARGET} PROPERTIES
FRAMEWORK TRUE
FRAMEWORK_VERSION C
MACOSX_FRAMEWORK_NAME "${IMPL_TARGET}"
MACOSX_FRAMEWORK_IDENTIFIER "org.openal-soft.openal"
MACOSX_FRAMEWORK_SHORT_VERSION_STRING ${LIB_VERSION}
MACOSX_FRAMEWORK_BUNDLE_VERSION ${BUNDLE_VERSION}
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ""
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO"
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO"
PUBLIC_HEADER "${TARGET_PUBLIC_HEADERS}"
MACOSX_RPATH TRUE)
!!!重要: 以上脚本设置CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG
和MACOSX_RPATH TRUE
是关键,少一个都不行。
自此openal-soft最新master可以非方便地通过如下脚本编译生成iOS framework了
git clone https://github.com/kcat/openal-soft
cd openal-soft
# build device
cmake -S . -GXcode -DCMAKE_SYSTEM_NAME=iOS -DALSOFT_OSX_FRAMEWORK=ON -B build_arm "-DCMAKE_OSX_ARCHITECTURES=armv7;arm64"
cmake --build build_arm --config Release --target OpenAL
# build simulator64
cmake -S . -GXcode -DCMAKE_SYSTEM_NAME=iOS -DALSOFT_OSX_FRAMEWORK=ON -B build_x64 -DCMAKE_OSX_SYSROOT=iphonesimulator
cmake --build build_x64 --config Release --target OpenAL
# combine device & simulator dyn libs to one flat-file
cp -r build_arm/Release-iphoneos/soft_oal.framework ./
lipo -create build_arm/Release-iphoneos/soft_oal.framework/soft_oal build_x64/Release-iphonesimulator/soft_oal.framework/soft_oal -output ./soft_oal.framework/soft_oal
# check the flat lib
lipo -info soft_oal.framework/soft_oal
结语
虽然我们现在可以很方便的编译生成 soft_oal.framework
, 但是目前由于CMake还不支持自动将ios App连接的第三方dynamic framework自动设置为 Embedded Content
,因此需要手动拖拽到xcode工程的General/Embedded Content
属性下生成的ipa才能正常在设备上跑。
然而根据CMake官方issue#18073, cmake-3.20发布后,也许就支持了,让我们静静地期待吧。。。