在我們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ù)類似的做法厘熟,指定abiFilters
與path
參數(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中armeabi
、mips
就缆、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é)果如下:
這樣,我們就生成了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, ¤t_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è)示例工程,也是夠用了~