android studio如何通过cmake生成可执行程序

在我们NDK开发中,可能会遇到需要通过cmake生成一个可执行程序,在app运行的过程中,调用该可执行程序用于做一些底层操作,尽管我们做的更多的可能是通过java直接调用JNI接口的方式来调用底层c/c++接口

开发环境

操作系统:macOS 10.14.3
ndk版本:android-ndk-r19

示例

这里以opengl es来作为参考示例,编写一个可以在android平台用于测试音频的可执行程序

build.gradle

首先我们需要在build.gradle文件中,与编译动态库或者静态库类似的做法,指定abiFilterspath参数,其中abiFilters指定编译的目标平台,path指定cmake文件路径,参考代码如下:

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.eggsy.opensles"
        minSdkVersion 16
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        externalNativeBuild {
            cmake {
                cppFlags ""
                abiFilters 'armeabi-v7a', 'arm64-v8a' , 'x86', 'x86_64'
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

可能会有人有疑问,为什么abiFilters中armeabimipsmips64等平台呢,因为当前ndk版本是r19,从NDK r17版本开始,已经去掉了armeabi、mips、mips64的ABI支持,所以我们不需要在abiFilters中使用以上配置。

CMakeLists.txt

上面我们定义了abiFilter,并指定了cmake文件的文件名,下面我们看下CMakeLists.txt是怎么写的

# 指定当前cmake支持的最低版本
cmake_minimum_required(VERSION 3.4.1)
# 指定输出的目录结构,这里我们指定到当前CMakeLists.txt同级目录的/src/main/assets/目录下,
# 根据abiFilter中指定的编译平台在细分子目录
set(EXECUTABLE_OUTPUT_PATH      "${CMAKE_CURRENT_SOURCE_DIR}/src/main/assets/${ANDROID_ABI}")
# 指定编译的目标可执行程序名称与编译的文件,其中testopensl是输出的可执行程序名称,src/main/cpp/opensltest.cpp是
# 编译到可执行程序中的文件,可指定多个
add_executable(testopensl src/main/cpp/opensltest.cpp)
# 添加目标编译目录
#target_include_directories (testopensl PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

# 从ANDROID_NDK变量表示的目录下,找到SLES/OpenSLES.h路径,存储在SL_INCLUDE_DIR变量中备用
find_path(SL_INCLUDE_DIR SLES/OpenSLES.h
        HINTS ${ANDROID_NDK})

# 从上面find_path中找到的SL_INCLUDE_DIR路径的./../lib目录中,找到libOpenSLES.so动态库,存储在SL_LIBRARY变量
# 中备用
find_library(SL_LIBRARY libOpenSLES.so
        HINTS ${SL_INCLUDE_DIR}/../lib)

# 指定编译器编译时寻找头文件的目录
include_directories(
        ${CMAKE_SOURCE_DIR}/src/main/cpp   #此处忽略
        ${CMAKE_SOURCE_DIR}/libs/include   #此处忽略
        ${SL_INCLUDE_DIR}                  #把头文件路径添加进来
)

# 链接目标程序与库
target_link_libraries(
        testopensl
        ${SL_LIBRARY}     #把opensl库文件添加进来
)

生成目标文件

通过android studio中Make Project,会在我们CMakeLists.txt中指定的EXECUTABLE_OUTPUT_PATH路径下生成对应的可执行程序,生成结果如下:


image.png

这样,我们就生成了testopensl可执行程序

验证

我们将这个可执行程序通过adb shell拷贝到手机上,并且chmod +x testopensl增加可执行权限后,就可以在android系统终端执行程序查看结果了,或者在android代码中通过Runtime.getRuntime().exec()的几个方法,来执行可执行程序,这里我就不做过多解释。

参考opensltest.cpp文件

#include <jni.h>
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>

extern "C"

#define sample_size (44100 * 2 *2)

// 引擎接口
SLObjectItf engineObject = NULL;
SLEngineItf engineEngine = NULL;

//混音器
SLObjectItf outputMixObject = NULL;
SLEnvironmentalReverbItf outputMixEnvironmentalReverb = NULL;
SLEnvironmentalReverbSettings reverbSettings = SL_I3DL2_ENVIRONMENT_PRESET_STONECORRIDOR;

//pcm
SLObjectItf pcmPlayerObject = NULL;
SLPlayItf pcmPlayerPlay = NULL;
SLVolumeItf pcmPlayerVolume = NULL;

//缓冲器队列接口
SLAndroidSimpleBufferQueueItf pcmBufferQueue;

uint8_t *out_buffer;

FILE *pcmFile;
void *buffer;

unsigned long get_file_size(const char *path) {
    unsigned long filesize = -1;
    struct stat statbuff;
    if (stat(path, &statbuff) < 0) {
        return filesize;
    } else {
        filesize = statbuff.st_size;
    }
    return filesize;
}

void create_engine() {
    SLresult result;//返回结果
    result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);//第一步创建引擎
    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);//实现(Realize)engineObject接口对象
    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE,
                                           &engineEngine);//通过engineObject的GetInterface方法初始化engineEngine
}

void get_pcm_data(void **pcm) {
    while (!feof(pcmFile)) {
        fread(out_buffer, 44100 * 2 * 2, 1, pcmFile);
        if (out_buffer == NULL) {
            printf("%s", "read end \n");
            break;
        } else {
            printf("%s", "reading \n");
        }
        *pcm = out_buffer;
        break;
    }
}

void pcm_buffer_callBack(SLAndroidSimpleBufferQueueItf bf, void *context) {
    get_pcm_data(&buffer);
    if (NULL != buffer) {
        SLresult result;
        result = (*pcmBufferQueue)->Enqueue(pcmBufferQueue, buffer, 44100 * 2 * 2);
    }
}

void init() {
    out_buffer = (uint8_t *) malloc(44100 * 2 * 2);
    SLresult result;
    //混音器
    SLObjectItf outputMixObject = NULL;//用SLObjectItf创建混音器接口对象
    SLEnvironmentalReverbItf outputMixEnvironmentalReverb = NULL;////创建具体的混音器对象实例

    //第一步,创建引擎
    create_engine();

    //第二步,创建混音器
    const SLInterfaceID mids[1] = {SL_IID_ENVIRONMENTALREVERB};
    const SLboolean mreq[1] = {SL_BOOLEAN_FALSE};
    result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, mids, mreq);
    (void) result;
    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    (void) result;
    result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB,
                                              &outputMixEnvironmentalReverb);
    if (SL_RESULT_SUCCESS == result) {
        result = (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(
                outputMixEnvironmentalReverb, &reverbSettings);
        (void) result;
    }
    SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    SLDataSink audioSnk = {&outputMix, NULL};


    // 第三步,配置PCM格式信息
    SLDataLocator_AndroidSimpleBufferQueue android_queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
                                                            2};
    SLDataFormat_PCM pcm = {
            SL_DATAFORMAT_PCM,//播放pcm格式的数据
            2,//2个声道(立体声)
            SL_SAMPLINGRATE_44_1,//44100hz的频率
            SL_PCMSAMPLEFORMAT_FIXED_16,//位数 16位
            SL_PCMSAMPLEFORMAT_FIXED_16,//和位数一致就行
            SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,//立体声(前左前右)
            SL_BYTEORDER_LITTLEENDIAN//结束标志
    };
    SLDataSource slDataSource = {&android_queue, &pcm};


    const SLInterfaceID ids[3] = {SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND, SL_IID_VOLUME};
    const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};

    result = (*engineEngine)->CreateAudioPlayer(engineEngine, &pcmPlayerObject, &slDataSource,
                                                &audioSnk, 3, ids, req);
    //初始化播放器
    (*pcmPlayerObject)->Realize(pcmPlayerObject, SL_BOOLEAN_FALSE);

//    得到接口后调用  获取Player接口
    (*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_PLAY, &pcmPlayerPlay);

//    注册回调缓冲区 获取缓冲队列接口
    (*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_BUFFERQUEUE, &pcmBufferQueue);
    //缓冲接口回调
    (*pcmBufferQueue)->RegisterCallback(pcmBufferQueue, pcm_buffer_callBack, NULL);
//    获取音量接口
    (*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_VOLUME, &pcmPlayerVolume);

//    获取播放状态接口
    (*pcmPlayerPlay)->SetPlayState(pcmPlayerPlay, SL_PLAYSTATE_PLAYING);
}

void play_audio(char *filename) {
    printf("input pcm file path %s \n", filename);
    pcmFile = fopen(filename, "r");
    if (pcmFile == NULL) {
        printf("%s", "fopen file error \n");
        return;
    }

    init();

//  主动调用回调函数开始工作
    pcm_buffer_callBack(pcmBufferQueue, NULL);
}

void release() {

    if (pcmPlayerObject != NULL) {
        (*pcmPlayerObject)->Destroy(pcmPlayerObject);
        pcmPlayerObject = NULL;
        pcmPlayerPlay = NULL;
        pcmPlayerVolume = NULL;
        pcmBufferQueue = NULL;
        pcmFile = NULL;
        buffer = NULL;
        out_buffer = NULL;
    }

    // destroy output mix object, and invalidate all associated interfaces
    if (outputMixObject != NULL) {
        (*outputMixObject)->Destroy(outputMixObject);
        outputMixObject = NULL;
        outputMixEnvironmentalReverb = NULL;
    }

    // destroy engine object, and invalidate all associated interfaces
    if (engineObject != NULL) {
        (*engineObject)->Destroy(engineObject);
        engineObject = NULL;
        engineEngine = NULL;
    }

}

int main(int argc, char *argv[]) {
    int count;
    char *pcm_path;
    long file_size;
    int sleep_seconds;
    char *cmd;
    char *vol;
    int volume = -1;
    SLmillibel current_volume = -1;
    if (argc < 2) {
        printf("please input pcm file path \n");
    } else if (argc == 2) {
        cmd = argv[1];
        printf("cmd : %s \n", cmd);
        if (memcmp(cmd, "vol", strlen("vol")) == 0) {
            init();
            if (pcmPlayerVolume != NULL) {
                (*pcmPlayerVolume)->GetVolumeLevel(pcmPlayerVolume, &current_volume);
            }
            release();
            printf("current volume %d\n", current_volume);
        } else {
            printf("begin play pcm data \n");
            printf("pcm data size %d sample size %d\n", file_size, sample_size);
            pcm_path = argv[1];
            play_audio(pcm_path);
            file_size = get_file_size(pcm_path);
            if (file_size > 0) {
                if (file_size % sample_size == 0) {
                    sleep_seconds = (file_size % sample_size);
                } else {
                    sleep_seconds = ((int) file_size / sample_size + 1);
                }
                sleep(sleep_seconds);
            }
            release();
            printf("end play pcm data \n");
        }
    } else if (argc == 3) {
        cmd = argv[1];
        vol = argv[2];
        if (memcmp(cmd, "vol", strlen("vol")) == 0) {
            init();
            // 音量设置
            printf("begin set volume \n");
            volume = atoi(vol);
            if (volume >= 0 && pcmPlayerVolume != NULL) {
                printf("set volume %d\n", volume);
                (*pcmPlayerVolume)->SetVolumeLevel(pcmPlayerVolume, volume);
            }
            release();
            printf("end set volume \n");
        }
    }
    return 0;
}

最后找一段pcm文件,来进行播放测试吧,注意是需要44.1khz的pcm16音频数据就可以进行播放啦!!!

总结

以上就是android studio上用cmake生成可执行程序的一些步骤,由于是测试方法,testopensl.cpp并没有做成很通用的播放,不过作为一个示例工程,也是够用了~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352