Android音频开发之使用OpenSL ES API
2017-01-02 14:54
513 查看
本文主要记录android端音频使用NDK开发
Android Studio 2.2 提供了CMake 来开发NDK , 只能说更加方便了,没有升级的赶紧去升级吧
相关guides : https://developer.android.com/ndk/guides/audio/index.html
如果Android使用c++还不会的同学转中文api : https://developer.android.com/studio/projects/add-native-code.html#download-ndk
open sl 文档 https://www.khronos.org/registry/sles/specs/OpenSL_ES_Specification_1.0.1.pdf
老外的一篇文章可以参考 https://audioprograming.wordpress.com/2012/03/03/android-audio-streaming-with-opensl-es-and-the-ndk/
google给了两个例子
audio-echo 和 native-audio 先拿例子练手
参考老外的写的测试代码:https://github.com/CL-window/audio_openSL
opensl_io.h 用到的主要的四个函数
分别是 打开/关闭android 音频设备 写入音频数据和读取音频数据
看c 代码之前,关于指针的几个小知识点需要先温习一下下
/** needs know
* -> 是在引用结构体中的变量
* 结构体实例可以通过使用 ‘.’ 符号访问变量。对于结构体实例的指针,我们可以通过 ‘->’ 符号访问变量
* * 当用在声明一个变量时,*表示这里声明了一个指针。其它情况用到*表示指针的取值
* & 地址操作符,用来引用一个内存地址
*/
/*
Open the audio device with a given sampling rate (sr), input and output channels and IO buffer size
in frames. Returns a handle to the OpenSL stream
*/
OPENSL_STREAM* android_OpenAudioDevice(int sr, int inchannels, int outchannels, int bufferframes);
/*
Close the audio device
*/
void android_CloseAudioDevice(OPENSL_STREAM *p);
/*
Read a buffer from the OpenSL stream *p, of size samples. Returns the number of samples read.
*/
int android_AudioIn(OPENSL_STREAM *p, float *buffer,int size);
/*
Write a buffer to the OpenSL stream *p, of size samples. Returns the number of samples written.
*/
int android_AudioOut(OPENSL_STREAM *p, float *buffer,int size);
需要注意的是,播放和录制audio需要在子线程里运行,我使用的是 HandlerThread
只是使用了一个文件 opensl_io.c 和 opensl_io.h
原作者有三个文件 opensl_io.c opensl_io2.c 和 opensl_io3.c
opensl_io.c 是使用线程锁来控制录制和播放
opensl_io2.c 使用 google 推荐的 回调 SLAndroidSimpleBufferQueueItf
opensl_io3.c 也是使用回调,不过播放回调包含了audio信息
audio_recode_play.cpp 文件
创建Audio Engine
音频录制
录制 audio
综上 使用OpenSL相关API的通用步骤是:
1.创建对象(通过带有create的函数)
2.初始化(通过Realize函数)
3.获取接口来使用相关功能(通过GetInterface函数)
Android Studio 2.2 提供了CMake 来开发NDK , 只能说更加方便了,没有升级的赶紧去升级吧
相关guides : https://developer.android.com/ndk/guides/audio/index.html
如果Android使用c++还不会的同学转中文api : https://developer.android.com/studio/projects/add-native-code.html#download-ndk
open sl 文档 https://www.khronos.org/registry/sles/specs/OpenSL_ES_Specification_1.0.1.pdf
老外的一篇文章可以参考 https://audioprograming.wordpress.com/2012/03/03/android-audio-streaming-with-opensl-es-and-the-ndk/
google给了两个例子
audio-echo 和 native-audio 先拿例子练手
参考老外的写的测试代码:https://github.com/CL-window/audio_openSL
opensl_io.h 用到的主要的四个函数
分别是 打开/关闭android 音频设备 写入音频数据和读取音频数据
看c 代码之前,关于指针的几个小知识点需要先温习一下下
/** needs know
* -> 是在引用结构体中的变量
* 结构体实例可以通过使用 ‘.’ 符号访问变量。对于结构体实例的指针,我们可以通过 ‘->’ 符号访问变量
* * 当用在声明一个变量时,*表示这里声明了一个指针。其它情况用到*表示指针的取值
* & 地址操作符,用来引用一个内存地址
*/
/*
Open the audio device with a given sampling rate (sr), input and output channels and IO buffer size
in frames. Returns a handle to the OpenSL stream
*/
OPENSL_STREAM* android_OpenAudioDevice(int sr, int inchannels, int outchannels, int bufferframes);
/*
Close the audio device
*/
void android_CloseAudioDevice(OPENSL_STREAM *p);
/*
Read a buffer from the OpenSL stream *p, of size samples. Returns the number of samples read.
*/
int android_AudioIn(OPENSL_STREAM *p, float *buffer,int size);
/*
Write a buffer to the OpenSL stream *p, of size samples. Returns the number of samples written.
*/
int android_AudioOut(OPENSL_STREAM *p, float *buffer,int size);
需要注意的是,播放和录制audio需要在子线程里运行,我使用的是 HandlerThread
只是使用了一个文件 opensl_io.c 和 opensl_io.h
原作者有三个文件 opensl_io.c opensl_io2.c 和 opensl_io3.c
opensl_io.c 是使用线程锁来控制录制和播放
opensl_io2.c 使用 google 推荐的 回调 SLAndroidSimpleBufferQueueItf
opensl_io3.c 也是使用回调,不过播放回调包含了audio信息
audio_recode_play.cpp 文件
// // Created by slack on 2016/12/27. // #include <stdio.h> #include <android/log.h> #include "opensl_io.h" #include "jni.h" #define LOG(...) __android_log_print(ANDROID_LOG_DEBUG,"AudioDemo-JNI",__VA_ARGS__) #define SAMPLERATE 44100 #define CHANNELS 1 #define PERIOD_TIME 20 //ms #define FRAME_SIZE SAMPLERATE*PERIOD_TIME/1000 #define BUFFER_SIZE FRAME_SIZE*CHANNELS #define TEST_CAPTURE_FILE_PATH "/sdcard/audio.pcm" static volatile int g_loop_exit = 0; #ifdef __cplusplus extern "C" { #endif JNIEXPORT jboolean JNICALL Java_com_cl_slack_audio_1foreign_AudioJNI_startRecodeAudio(JNIEnv *env, jclass type) { FILE * fp = fopen(TEST_CAPTURE_FILE_PATH, "wb"); if( fp == NULL ) { LOG("cannot open file (%s)\n", TEST_CAPTURE_FILE_PATH); return -1; } OPENSL_STREAM* stream = android_OpenAudioDevice(SAMPLERATE, CHANNELS, CHANNELS, FRAME_SIZE); if (stream == NULL) { fclose(fp); LOG("failed to open audio device ! \n"); return JNI_FALSE; } int samples; float buffer[BUFFER_SIZE]; g_loop_exit = 0; while (!g_loop_exit) { samples = android_AudioIn(stream, buffer, BUFFER_SIZE); if (samples < 0) { LOG("android_AudioIn failed !\n"); break; } if (fwrite((unsigned char *)buffer, samples*sizeof(short), 1, fp) != 1) { LOG("failed to save captured data !\n "); break; } LOG("capture %d samples !\n", samples); } android_CloseAudioDevice(stream); fclose(fp); LOG("nativeStartCapture completed !"); return JNI_TRUE; } JNIEXPORT jboolean JNICALL Java_com_cl_slack_audio_1foreign_AudioJNI_stopRecodeAudio(JNIEnv *env, jclass type) { g_loop_exit = 1; return JNI_TRUE; } JNIEXPORT jboolean JNICALL Java_com_cl_slack_audio_1foreign_AudioJNI_startPlayAudio(JNIEnv *env, jclass type) { FILE * fp = fopen(TEST_CAPTURE_FILE_PATH, "rb"); if( fp == NULL ) { LOG("cannot open file (%s) !\n",TEST_CAPTURE_FILE_PATH); return -1; } OPENSL_STREAM* stream = android_OpenAudioDevice(SAMPLERATE, CHANNELS, CHANNELS, FRAME_SIZE); if (stream == NULL) { fclose(fp); LOG("failed to open audio device ! \n"); return JNI_FALSE; } int samples; float buffer[BUFFER_SIZE]; g_loop_exit = 0; while (!g_loop_exit && !feof(fp)) { if (fread((unsigned char *)buffer, BUFFER_SIZE*2, 1, fp) != 1) { LOG("failed to read data \n "); break; } samples = android_AudioOut(stream, buffer, BUFFER_SIZE); if (samples < 0) { LOG("android_AudioOut failed !\n"); } LOG("playback %d samples !\n", samples); } android_CloseAudioDevice(stream); fclose(fp); LOG("nativeStartPlayback completed !"); return JNI_TRUE; } JNIEXPORT jboolean JNICALL Java_com_cl_slack_audio_1foreign_AudioJNI_stopPlayAudio(JNIEnv *env, jclass type) { g_loop_exit = 1; return JNI_TRUE; } #ifdef __cplusplus } #endif
创建Audio Engine
// create engine 创建Audio Engine result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); assert(SL_RESULT_SUCCESS == result); (void)result; // realize the engine 初始化上一步得到的engineObject result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); assert(SL_RESULT_SUCCESS == result); (void)result; // get the engine interface, which is needed in order to create other objects // 获取 SLEngineItf 接口对象,后续的操作将使用这个对象 result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); assert(SL_RESULT_SUCCESS == result); (void)result; // create output mix, with environmental reverb specified as a non-required interface // 使用 engineEngine,创建音频输出 outputMixObject const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB}; const SLboolean req[1] = {SL_BOOLEAN_FALSE}; result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids, req); assert(SL_RESULT_SUCCESS == result); (void)result; // realize the output mix 初始化 outputMixObject result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
音频录制
// create audio recorder 创建AudioRecorder // (requires the RECORD_AUDIO permission) const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; const SLboolean req[1] = {SL_BOOLEAN_TRUE}; result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &audioSrc, &audioSnk, 1, id, req); if (SL_RESULT_SUCCESS != result) { return JNI_FALSE; } // realize the audio recorder 初始化AudioRecorder result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE); if (SL_RESULT_SUCCESS != result) { return JNI_FALSE; } // get the record interface 获取录制器接口 result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecord); assert(SL_RESULT_SUCCESS == result); (void)result; // get the buffer queue interface 获取音频输入的BufferQueue接口 result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &recorderBufferQueue); assert(SL_RESULT_SUCCESS == result); (void)result; // register callback on the buffer queue result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, bqRecorderCallback, NULL); assert(SL_RESULT_SUCCESS == result); (void)result;
录制 audio
// in case already recording, stop recording and clear buffer queue result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED); assert(SL_RESULT_SUCCESS == result); (void)result; result = (*recorderBufferQueue)->Clear(recorderBufferQueue); assert(SL_RESULT_SUCCESS == result); (void)result; // the buffer is not valid for playback yet recorderSize = 0; // enqueue an empty buffer to be filled by the recorder // (for streaming recording, we would enqueue at least 2 empty buffers to start things off) result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recorderBuffer, RECORDER_FRAMES * sizeof(short)); // the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT, // which for this code example would indicate a programming error assert(SL_RESULT_SUCCESS == result); (void)result; // start recording result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING); assert(SL_RESULT_SUCCESS == result); (void)result;音频播放 audio player
// configure audio source Buffer Queue的参数 和 音频格式 SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, 1, SL_SAMPLINGRATE_8, SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN}; // 输出源 SLDataSource audioSrc = {&loc_bufq, &format_pcm}; // configure audio sink 输出管道 SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject}; SLDataSink audioSnk = {&loc_outmix, NULL}; const SLInterfaceID ids[3] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME, SL_IID_EFFECTSEND, /*SL_IID_MUTESOLO,*/}; const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, /*SL_BOOLEAN_TRUE,*/ }; // 创建音频播放对象AudioPlayer result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, bqPlayerSampleRate? 2 : 3, ids, req); assert(SL_RESULT_SUCCESS == result); (void)result; // realize the player 初始化AudioPlayer result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE); assert(SL_RESULT_SUCCESS == result); (void)result; // get the play interface 获取播放器接口 result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay); assert(SL_RESULT_SUCCESS == result); (void)result; // get the buffer queue interface 获取音频输出的SLAndroidSimpleBufferQueueItf bqPlayerPlay接口 result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue); assert(SL_RESULT_SUCCESS == result); (void)result; // register callback on the buffer queue result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, NULL); assert(SL_RESULT_SUCCESS == result); (void)result;
综上 使用OpenSL相关API的通用步骤是:
1.创建对象(通过带有create的函数)
2.初始化(通过Realize函数)
3.获取接口来使用相关功能(通过GetInterface函数)
相关文章推荐
- Android音频开发(7):使用 OpenSL ES API(下)
- Android音频开发(7):使用 OpenSL ES API(下)
- Android音频开发(7):使用 OpenSL ES API(下)
- Android音频开发(7):使用 OpenSL ES API(下)
- Android音频开发(6):使用 OpenSL ES API(上)
- Android音频开发(6):使用 OpenSL ES API(上)
- Android音频开发(6):使用 OpenSL ES API(上)
- Android音频开发(6):使用 OpenSL ES API(上)
- Android音频开发(6):使用 OpenSL ES API(上)
- 转:Android应用开发中如何使用隐藏API
- 原创:Android应用开发记录-Andorid歌词秀(3)使用的Service的音频播放器
- android开发(37) android使用android_serialport_api 操作串口,解决权限问题
- Android 使用Facebook API 开发
- Android开发 使用Lame把音频文件转换成mp3格式
- Android开发:使用AudioTrack播放PCM音频数据【附源码】
- Android项目开发五-《星星生活志》1.使用MediaRecorder录制音频
- Android开发 使用Lame把音频文件转换成mp3格式
- android 开发之 使用Android系统隐藏api 读取*.apk 程序安装包信息
- Android应用开发中如何使用隐藏的API
- android开发教程之获取使用当前api的应用程序名称