NDK学习笔记:一起来变萝莉音!FMOD学习总结(上)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a360940265a/article/details/82835378

NDK学习笔记:一起来变萝莉音!FMOD学习总结(上)

一、不是fmod(),不是/fmod,是FMOD!

关于 FMOD,总感觉全网关于它的文章不多,也不够细,Android平台上的总结更是如数家珍。可能是FMOD自身没有中文说明介绍的原因吧,另外一个原因可能就是闭源商用付费吧。我自己是在2016年末,在一个在线教学(TX的动脑学院)接触使用这个音频开发库的,算是接触NDK音视频的第一步,在总结编写这篇文章之前我还连官方网站的链接都记错(大写的尴尬)废话不多,开始进入正题,先来段百度百科的简介:

Fmod 声音系统是为游戏开发者准备的革命性音频引擎。目前最新版本是Fmod Studio 1.10.08。你也可以在FMOD的官网上下载到FMOD 3。如今采用了FMOD作为音频引擎的游戏包括Far Cry(孤岛惊魂)、Tom Clancy's Ghost Recon(幽灵行动),甚至著名的World Of Warcraft(魔兽争霸),QQ变音模块,万王之王(手游版魔兽世界,tx爸爸出品)等等。如今Fmod已经完全成为一个多声道混音器(a full multichannel mixer)。2D声音可以用5.1甚至7.1的形式播放。声音可以交换彼此分配到的声道,举个例子,一个3D立体声的左右声道可以相互交换、混音或是全都通过左扬声器播放出来。Fmod可以实现这一特性是由于它支持pan matrices(声像矩阵)。任何输入声音频道都可以被重定向到任意输出扬声器,并且支持紧接着的这个百分比层,因此可以说没有一个绝对的扬声器分配方案。这句话我不是很理解,原文是:The way this is available is FMOD supports pan matrices. Any input sound channel can be redirected to any output speaker,and on top of this percentages/fractional levels are supported,so there are no absolute speaker assignments.
为了满足高端音效设备,Fmod借助ASIO(Audio Stream Input Output,音频设备零延迟)功能,支持16个输出通道的多通话线路输出(multichannel output)。更多关于的FMOD的简介请点击这里

好了,废话就到这里了。现在就让我开始Fmod的撸码旅程。

二、准备Fmod

通过百度百科的介绍,我们到Fmod的官方网站,然后注册登陆之后,点击download跳转到下载页面。初步浏览一下网页之后找到FMOD Studio,分页下包括FMOD Studio Tool / FMOD Studio API。FMOD Studio Tool是一款专业处理音频的工具软件,类似会声会影那一类工具。然后我们的重点就放在FMOD Studio API上,介绍如下:FMOD Studio API is the interface for programmers. There are 2 APIs included. The FMOD Studio API and the Low Level API. For further details, see the revision history. Major version number changes include major updates which may require migration. 1.10 API is not binary compatible with 1.09 API.  重点我都已经帮大家画出来了,这套API里面包含了两套代码,然后就是1.10的API并没有完全兼容1.09,啥意思?两套API的划分就类似Android的Camera和Camera2,而1.10的API接口和1.09的相比较,可能有更改或删减的变化,所以为了兼容和稳定方面考虑,我们下载1.09.10版本的Android API资料包。

下载解压之后,我们得到一个fmodstudioapi10910android的文件夹,里面包含了四个组成部分如下,

plugins是插件部分,里面是什么google_vr,然而我到现在还不知道是啥玩意,忽略。

doc是文档,但文档大部分是说FMOD Sutiod Tool,一些其他描述,并不是API的查考文档,有时间可以大概浏览一番。

bin是fsbank部分相关的二进制文件。fsbank是FS里面配合FS Tool 所独有的一个模块,也不是我们需要关注的重点。适合非常专业的音频发烧者研究研究。

api才是我们需要关注的,里面又分为三个部分,分别是fsbank / lowlevel / studio ,fsbank已经在上面bin的介绍过,bin里面包含的是windows版本的lib和dll,这里的是头文件和so库;然后lowlevel、studio分别对应FMOD的两套APIs,包含了头文件及其库,还要对应的example例程。然而... 你以为有example就so easy了吗?呵呵,too young to simple ...

三、阅读FMOD Example

我们以lowlevel的example开始,探索一下FMOD的example要怎么搞。首先我们看看example都有些什么:

一个java文件夹,依次进去这个文件夹你就会立刻发现,这里面就是Android的MainActivity项目入口文件;media里面的是一些使用到的音频素材,各种格式应有尽有;plugins还是插件的部分,不懂,忽略;vs2010里面的是VisualStudio版本的Android工程文件,也是需要捣鼓一番才能跑起来。剩下的,在example目录下的 h / cpp文件就是demo了。没错,这些就是demo了。

我们找一个简单的感兴趣的play_sound.cpp开始,打开文件就一个FMOD_Main,然后例程代码如下:

#include "fmod.hpp"
#include "common.h"

int FMOD_Main()
{
    FMOD::System     *system;
    FMOD::Sound      *sound1, *sound2, *sound3;
    FMOD::Channel    *channel = 0;
    FMOD_RESULT       result;
    unsigned int      version;
    void             *extradriverdata = 0;
    
    Common_Init(&extradriverdata);

    /*
        Create a System object and initialize
    */
    result = FMOD::System_Create(&system);
    ERRCHECK(result);

    result = system->getVersion(&version);
    ERRCHECK(result);

    if (version < FMOD_VERSION)
    {
        Common_Fatal("FMOD lib version %08x doesn't match header version %08x", version, FMOD_VERSION);
    }

    result = system->init(32, FMOD_INIT_NORMAL, extradriverdata);
    ERRCHECK(result);

    result = system->createSound(Common_MediaPath("drumloop.wav"), FMOD_DEFAULT, 0, &sound1);
    ERRCHECK(result);

    result = sound1->setMode(FMOD_LOOP_OFF);    /* drumloop.wav has embedded loop points which automatically makes looping turn on, */
    ERRCHECK(result);                           /* so turn it off here.  We could have also just put FMOD_LOOP_OFF in the above CreateSound call. */

    result = system->createSound(Common_MediaPath("jaguar.wav"), FMOD_DEFAULT, 0, &sound2);
    ERRCHECK(result);

    result = system->createSound(Common_MediaPath("swish.wav"), FMOD_DEFAULT, 0, &sound3);
    ERRCHECK(result);

    ... ...
}

初步浏览代码我们可以知道,Common系列的API很重要。然后我们以Common_MediaPath这个API为例,看看play_sound的依赖关系,它一开始包含了两个头文件,一个fmod.hpp,另外一个是common.h,从命名规则考虑,我们可以发现Common_MediaPath的定义就在common.h,然而你到common.cpp是找不到Common_MediaPath的实现的,哈哈哈哈!why?

因为 common.h 又包含了两个头文件 common_platform.h / fmod.h,又是命名规则出发,到common_platform.cpp,这下我们就找到了Common_MediaPath的实现了,我们看看源码:

const char *Common_MediaPath(const char *fileName)
{
    char *filePath = (char *)calloc(256, sizeof(char));

    strcat(filePath, "file:///android_asset/");
    strcat(filePath, fileName);
    gPathList.push_back(filePath);

    return filePath;
}

从源代码我们可以知道,Common_MediaPath都是基于asset的目录下寻找资源文件,而且文件命名长度不能超过256-"file:///android_asset/".length 个字符。

(如果一些文件不好找在哪个目录,建议大家在电脑上装一个软件,everything,全局搜索贼快。)

好的,c++代码的阅读方式就是这么一个大概的流程。我们回到example/java目录,快速浏览一下Android的入口:

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
    	super.onCreate(savedInstanceState);

        org.fmod.FMOD.init(this);
        
        mThread = new Thread(this, "Example Main");
        mThread.start();
        
        setStateCreate(); // native
    }

逻辑我们先不着急,从onCreate出发,我们要知道需要引入org.fmod.FMOD.jar,还有相对应的so库,另外还有一个工作线程。一切准备就绪之后,我们就开始真正在AndroidStudio上跑一个play_sound的例程。

四、FMOD For Android

首先我们还是基于BlogApp的项目工程,这个AS工程当初一开始创建的时候就已经添加了c++的支持。(AS版本2.3、插件版本2.2.2、Gradle version 2.14.1 都是比较老的版本,提高大家的适应性)接着我就要发车了,抓紧时间上车。

第一步:我们把需要的demo文件全部都拷贝到工程对应的位置,如上图,然后我们把 \fmodstudioapi10910android\api\lowlevel\examples\java\org\fmod\example\MainActivity 复制到org.zzrblog.fmod.FmodActivity。把相关的cpp文件拷贝到cpp/fmod下,\api\lowlevel\inc目录下都是一些头文件,我们把整个inc都拷贝下来。

第二步:打开FmodActivity,修改一些包名路径之后,移动文件的最下面位置的静态代码块。去除fmodstudio、fmodstudioL、fmodstudioD的System.loadLibrary,因为我们只需用到Fmod的API,并不需要fmodstudio的功能。然后把System.loadLibrary("stlport_shared"); System.loadLibrary("example"); 暂时都先注销。

FmodActivity的修改还没结束,因为FmodActivity中的native方法还是红色错误状态的,这是因为AS编译器还找不到java层的native方法的cpp实现。我们打开cpp/fmod/common_platform.cpp 也是移动到最下方的位置,可以看到extern "C" 的各种jni接口,我们把原来的Java_org_fmod_example_MainActivity_methodName改为现在的包名Java_org_zzrblog_fmod_FmodActivity修改之后,AS编译器就能找到java层native方法的函数实现了。

第三步:我们依次打开cpp/fmod/下的所有h / cpp文件,注意上方include头文件的路径,因为我们把共用的头文件都放到了cpp/fmod/inc下,所以我们要手动的添加头文件的路径 inc/fmod.h 

第四步:我们到\fmodstudioapi10910android\api\lowlevel\lib下找出需要预加载的so动态库,我们把要使用到的armeabi和armeabi-v7a文件夹下libfmod.so / libfmodL.so,都复制拷贝到AS工程下cpp/fmod/prebuild/文件夹内。

第四步:这一步比较复杂而且是挺重要的,编写我们的CMakeList文件 及其 相关配置。

(1)首先我们到app下的build.gradle配置ANDROID_ABI的平台,通过abiFilters指定:

android {
    ... ...
    defaultConfig {
        applicationId "org.zzrblog.blogapp"
        externalNativeBuild {
            cmake {
                cppFlags ""
                abiFilters 'armeabi','armeabi-v7a'
            }
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

(2)之后我们开始编写CMakeLists.txt 的脚本文件。

cmake_minimum_required(VERSION 3.4.1)

# 设置生成的so动态库最后输出的路径
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})

add_library( # 生成动态库的名称
             fmod-voice-lib
             # 指定是动态库SO
             SHARED
             # 编译库的源代码文件
             src/main/cpp/fmod/play_sound.cpp
             src/main/cpp/fmod/common.cpp
             src/main/cpp/fmod/common_platform.cpp)
             
# 引入外部so fmod 供源文件编译
add_library(fmod
            SHARED
            IMPORTED )
set_target_properties(fmod PROPERTIES IMPORTED_LOCATION
            ${PROJECT_SOURCE_DIR}/src/main/cpp/fmod/prebuild/${ANDROID_ABI}/libfmod.so)
set_target_properties(fmod PROPERTIES LINKER_LANGUAGE CXX)

# 引入外部so fmodL 供源文件编译
add_library(fmodL
            SHARED
            IMPORTED )
set_target_properties(fmodL PROPERTIES IMPORTED_LOCATION
            ${PROJECT_SOURCE_DIR}/src/main/cpp/fmod/prebuild/${ANDROID_ABI}/libfmodL.so)
set_target_properties(fmodL PROPERTIES LINKER_LANGUAGE CXX)

# 在系统找出预编译的log-lib库,指定在CMake脚本下的别名为log
find_library( log-lib log )

target_link_libraries( # 指定目标链接库
                       fmod-voice-lib
                       # 添加预编译库到目标链接库中
                       ${log-lib}
                       fmod
                       fmodL)

CMakeLists.txt的编译脚本其实不难写,已经附带了些注释。重要一点就是搞清楚依赖关系,确认好Cmake的语法。

现在来解析解析这个CMake脚本。首先我们设置CMAKE_LIBRARY_OUTPUT_DIRECTORY 动态库输出路径为${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}。PROJECT_SOURCE_DIR和ANDROID_ABI是AS版CMake脚本的内置变量,为啥我要指定这个路径呢?这是因为这个目录就是AS默认加载SO的目录。(并不是${PROJECT_SOURCE_DIR}/libs 嘿嘿嘿)... ...随后我们按照示例,用add_library指定源文件编译生成动态库,然而只有源文件还是不够的,我们需要fmod和fmodL两个动态库,所以我们还是通过add_library将这两个动态库引入编译链。而且需要指明其路径和链接参数。... ...最后我们还是按照示例,从系统路径下找到log库,把所有的依赖关系理顺之后,我们通过target_link_libraries进行目标库的链接编译。That is all.

第五步:编写完CMake脚本之后,我们需要sync同步一下项目工程。然后rebuild-project。如无意外你会发现src/main之下就会出现jniLibs/ANDROID_ABI/生成 libfmod-voice-lib.so 然而先不要急着运行demo,我们需要在FmodActivity的静态代码块中加载libfmod-voice-lib.so。我们还需要手动的把libfmod.so和libfmodL.so拷贝对应的ANDROID_ABI目录下。最后的最后,我们到fmodstudioapi10910android\api\lowlevel\examples\media文件夹下把demo使用到的媒体文件,放到项目的assets资源文件夹。附带工程目录如下:

运行FmodActivity吧。

猜你喜欢

转载自blog.csdn.net/a360940265a/article/details/82835378