android studio如何通過(guò)cmake生成可執(zhí)行程序

在我們NDK開(kāi)發(fā)中,可能會(huì)遇到需要通過(guò)cmake生成一個(gè)可執(zhí)行程序溉贿,在app運(yùn)行的過(guò)程中,調(diào)用該可執(zhí)行程序用于做一些底層操作,盡管我們做的更多的可能是通過(guò)java直接調(diào)用JNI接口的方式來(lái)調(diào)用底層c/c++接口

開(kāi)發(fā)環(huán)境

操作系統(tǒng):macOS 10.14.3
ndk版本:android-ndk-r19

示例

這里以opengl es來(lái)作為參考示例瓮恭,編寫(xiě)一個(gè)可以在android平臺(tái)用于測(cè)試音頻的可執(zhí)行程序

build.gradle

首先我們需要在build.gradle文件中,與編譯動(dòng)態(tài)庫(kù)或者靜態(tài)庫(kù)類似的做法厘熟,指定abiFilterspath參數(shù)屯蹦,其中abiFilters指定編譯的目標(biāo)平臺(tái),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"
        }
    }
}

可能會(huì)有人有疑問(wèn)颇玷,為什么abiFilters中armeabimips就缆、mips64等平臺(tái)呢帖渠,因?yàn)楫?dāng)前ndk版本是r19,從NDK r17版本開(kāi)始竭宰,已經(jīng)去掉了armeabi空郊、mips、mips64的ABI支持切揭,所以我們不需要在abiFilters中使用以上配置狞甚。

CMakeLists.txt

上面我們定義了abiFilter,并指定了cmake文件的文件名廓旬,下面我們看下CMakeLists.txt是怎么寫(xiě)的

# 指定當(dāng)前cmake支持的最低版本
cmake_minimum_required(VERSION 3.4.1)
# 指定輸出的目錄結(jié)構(gòu)哼审,這里我們指定到當(dāng)前CMakeLists.txt同級(jí)目錄的/src/main/assets/目錄下,
# 根據(jù)abiFilter中指定的編譯平臺(tái)在細(xì)分子目錄
set(EXECUTABLE_OUTPUT_PATH      "${CMAKE_CURRENT_SOURCE_DIR}/src/main/assets/${ANDROID_ABI}")
# 指定編譯的目標(biāo)可執(zhí)行程序名稱與編譯的文件,其中testopensl是輸出的可執(zhí)行程序名稱涩盾,src/main/cpp/opensltest.cpp是
# 編譯到可執(zhí)行程序中的文件十气,可指定多個(gè)
add_executable(testopensl src/main/cpp/opensltest.cpp)
# 添加目標(biāo)編譯目錄
#target_include_directories (testopensl PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

# 從ANDROID_NDK變量表示的目錄下,找到SLES/OpenSLES.h路徑春霍,存儲(chǔ)在SL_INCLUDE_DIR變量中備用
find_path(SL_INCLUDE_DIR SLES/OpenSLES.h
        HINTS ${ANDROID_NDK})

# 從上面find_path中找到的SL_INCLUDE_DIR路徑的./../lib目錄中砸西,找到libOpenSLES.so動(dòng)態(tài)庫(kù),存儲(chǔ)在SL_LIBRARY變量
# 中備用
find_library(SL_LIBRARY libOpenSLES.so
        HINTS ${SL_INCLUDE_DIR}/../lib)

# 指定編譯器編譯時(shí)尋找頭文件的目錄
include_directories(
        ${CMAKE_SOURCE_DIR}/src/main/cpp   #此處忽略
        ${CMAKE_SOURCE_DIR}/libs/include   #此處忽略
        ${SL_INCLUDE_DIR}                  #把頭文件路徑添加進(jìn)來(lái)
)

# 鏈接目標(biāo)程序與庫(kù)
target_link_libraries(
        testopensl
        ${SL_LIBRARY}     #把opensl庫(kù)文件添加進(jìn)來(lái)
)

生成目標(biāo)文件

通過(guò)android studio中Make Project址儒,會(huì)在我們CMakeLists.txt中指定的EXECUTABLE_OUTPUT_PATH路徑下生成對(duì)應(yīng)的可執(zhí)行程序芹枷,生成結(jié)果如下:


image.png

這樣,我們就生成了testopensl可執(zhí)行程序

驗(yàn)證

我們將這個(gè)可執(zhí)行程序通過(guò)adb shell拷貝到手機(jī)上莲趣,并且chmod +x testopensl增加可執(zhí)行權(quán)限后鸳慈,就可以在android系統(tǒng)終端執(zhí)行程序查看結(jié)果了,或者在android代碼中通過(guò)Runtime.getRuntime().exec()的幾個(gè)方法喧伞,來(lái)執(zhí)行可執(zhí)行程序蝶涩,這里我就不做過(guò)多解釋。

參考o(jì)pensltest.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;

//緩沖器隊(duì)列接口
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;//返回結(jié)果
    result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);//第一步創(chuàng)建引擎
    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);//實(shí)現(xiàn)(Realize)engineObject接口對(duì)象
    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE,
                                           &engineEngine);//通過(guò)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創(chuàng)建混音器接口對(duì)象
    SLEnvironmentalReverbItf outputMixEnvironmentalReverb = NULL;////創(chuàng)建具體的混音器對(duì)象實(shí)例

    //第一步絮识,創(chuàng)建引擎
    create_engine();

    //第二步绿聘,創(chuàng)建混音器
    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格式的數(shù)據(jù)
            2,//2個(gè)聲道(立體聲)
            SL_SAMPLINGRATE_44_1,//44100hz的頻率
            SL_PCMSAMPLEFORMAT_FIXED_16,//位數(shù) 16位
            SL_PCMSAMPLEFORMAT_FIXED_16,//和位數(shù)一致就行
            SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,//立體聲(前左前右)
            SL_BYTEORDER_LITTLEENDIAN//結(jié)束標(biāo)志
    };
    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);

//    得到接口后調(diào)用  獲取Player接口
    (*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_PLAY, &pcmPlayerPlay);

//    注冊(cè)回調(diào)緩沖區(qū) 獲取緩沖隊(duì)列接口
    (*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_BUFFERQUEUE, &pcmBufferQueue);
    //緩沖接口回調(diào)
    (*pcmBufferQueue)->RegisterCallback(pcmBufferQueue, pcm_buffer_callBack, NULL);
//    獲取音量接口
    (*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_VOLUME, &pcmPlayerVolume);

//    獲取播放狀態(tài)接口
    (*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();

//  主動(dòng)調(diào)用回調(diào)函數(shù)開(kāi)始工作
    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();
            // 音量設(shè)置
            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文件次舌,來(lái)進(jìn)行播放測(cè)試吧熄攘,注意是需要44.1khz的pcm16音頻數(shù)據(jù)就可以進(jìn)行播放啦!1四睢挪圾!

總結(jié)

以上就是android studio上用cmake生成可執(zhí)行程序的一些步驟,由于是測(cè)試方法逐沙,testopensl.cpp并沒(méi)有做成很通用的播放哲思,不過(guò)作為一個(gè)示例工程,也是夠用了~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吩案,一起剝皮案震驚了整個(gè)濱河市棚赔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌徘郭,老刑警劉巖靠益,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異残揉,居然都是意外死亡胧后,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)抱环,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)壳快,“玉大人纸巷,你說(shuō)我怎么就攤上這事】籼担” “怎么了瘤旨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)凛驮。 經(jīng)常有香客問(wèn)我裆站,道長(zhǎng)条辟,這世上最難降的妖魔是什么黔夭? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮羽嫡,結(jié)果婚禮上本姥,老公的妹妹穿的比我還像新娘。我一直安慰自己杭棵,他們只是感情好婚惫,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著魂爪,像睡著了一般先舷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上滓侍,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天蒋川,我揣著相機(jī)與錄音,去河邊找鬼撩笆。 笑死捺球,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的夕冲。 我是一名探鬼主播氮兵,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼歹鱼!你這毒婦竟也來(lái)了泣栈?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤弥姻,失蹤者是張志新(化名)和其女友劉穎秩霍,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蚁阳,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡铃绒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了螺捐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颠悬。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡矮燎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出赔癌,到底是詐尸還是另有隱情诞外,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布灾票,位于F島的核電站峡谊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏刊苍。R本人自食惡果不足惜既们,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望正什。 院中可真熱鬧啥纸,春花似錦、人聲如沸婴氮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)主经。三九已至荣暮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間罩驻,已是汗流浹背穗酥。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鉴腻,地道東北人迷扇。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像爽哎,于是被迫代替她去往敵國(guó)和親蜓席。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348