OpenSL ES总结

article/2025/7/4 2:33:30

OpenSL ES - 嵌入式音频加速标准。OpenSL ES™ 是无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速API。它为嵌入式移动多媒体设备上的本地应用程序开发者提供标准化, 高性能,低响应时间的音频功能实现方法,并实现软/硬件音频性能的直接跨平台部署,降低执行难度,促进高级音频市场的发展。

OpenSL ES 提供了音频播放和音频采集的一套方案,但是并无法进行音频流的编解码,因此我们可以通过OpenSL ES来播放音频采样出来的原始pcm数据格式,和采样一段原始pcm数据出来,但是无法播放经过压缩的例如mp3的音频文件或流。(一些简单的格式貌似可以播放)

一、OpenSL ES的API:

OpenSL ES提供的是基于C语言的API,但是它是基于对象和接口的方式提供的,会采用面向对象的思想开发API。OpenSL的对象是对一组资源及其状态的抽象(注意有点类似java里面的抽象类的概念),同时他们提供了一套方法来生成实现获取对应不同功能的接口,(有点类似多态的概念,但跟java的接口又完全不一样),个人理解这里的接口其实是指的上面抽象化的具体实例。这样说的比概念化,不是很轻易的理解,下面我就通过自己的理解用白水话解释一遍,有不同理解的不要喷哈,只要如何方便自己的理解都是对的。

OpenSL ES因为是C语言实现,因此没有类与对象的概念,但是语言木有不表示编程的思想木有,因此将音频处理所需要到的资源进行抽象化(例如涉及到引擎,混音器,播放器,我把它们定义为一组资源,他们的创建和实现等的过程都抽象化成为结构体SLObjectItf)。结构体SLObjectItf内部有几个函数指针,在使用的时候可以通过这些函数指针Realize来实现一组资源,可以通过函数指针GetState来获取这组资源的状态,或者通过函数指针RegisterCallback来对这组资源注册回调函数。在想要改变这组资源状态的时候,可以使用函数指针GetInterface通过传递不同的ID值来获取不同的接口来改变这组资源的不同属性。例如,我们可以通过CreateAudioPlayer创建一组来播放原始音频的资源,暂且命名为AudioPlayer:

  • 可以在AudioPlayer这组资源中获取SLPlayItf接口并通过该接口来控制播放器播放或者暂停状态
  • 还可以从AudioPlayer这组资源中获取SLVolueItf接口来通过该接口来控制播放器的音量
  • 还可以从AudioPlayer这组资源中获取SLBufferQueueItf 接口并通过该接口为播放器填充队列的源数据
  • 可以从AudioPlay这组资源中获取SLEffectSendItf接口来实现音效发送的功能
  • 还可以AudioPlay这组资源中获取SLMuteSoloItf接口来实现声道是否关闭的功能

1、我们可以先看看对象接口(对某组资源的描述)的定义:

struct SLObjectItf_;
typedef const struct SLObjectItf_ * const * SLObjectItf;
struct SLObjectItf_ {//实现这组资源SLresult (*Realize) ( SLObjectItf self,SLboolean async);SLresult (*Resume) (SLObjectItf self,SLboolean async);SLresult (*GetState) (SLObjectItf self,SLuint32 * pState);//根据不同的ID来获取这组资源具有的不同接口(注意区分java接口,可以理解成属性)SLresult (*GetInterface) (SLObjectItf self,const SLInterfaceID iid,void * pInterface);SLresult (*RegisterCallback) (SLObjectItf self,slObjectCallback callback,void * pContext);void (*AbortAsyncOperation) (SLObjectItf self);void (*Destroy) (SLObjectItf self);SLresult (*SetPriority) (SLObjectItf self,SLint32 priority,SLboolean preemptable);SLresult (*GetPriority) (SLObjectItf self,SLint32 *pPriority,SLboolean *pPreemptable);SLresult (*SetLossOfControlInterfaces) (SLObjectItf self,SLint16 numInterfaces,SLInterfaceID * pInterfaceIDs,SLboolean enabled);
};

2、对象(资源)和接口的通用操作方式:

在使用OpenSL ES来进行原始音频数据的播放或采集,通常需要到下面接口(下文中所有的资源其实就是对opensl中定义的对象的别称,为了与java的一些概念的区分):

  • SLObjectItf :对象接口(个人理解成一组资源),可以通过函数*Create*来创建一组资源(对象接口),通过该对象接口调用函数指针Realize来对这组资源进行实现或者实例化,之后可以通过GetInterface函数获取这组资源的各种其他接口(来描述这组资源的不同属性),注意改函数获取出来的东西其实也是SLObjectItf 
  • SLEngineItf :引擎接口(opensl的万能资源),oensl所用到所有资源都需要通过调用SLEngineItf的create*函数来进行孵化,这就类似于树结构的根节点,使用slCreateEngine生成引擎对象接口(引擎资源),并通过它的GetInterface函数获取混音器对象接口和播放器对象接口,我们又可以通过播放器对象接口获取到音量控制对象接口,队列对象接口,音效对象接口等,注意区分java里面的接口
  • SLEnvironmentalReverbItf:环境回响音效接口(属于输出混音器资源),个人认为只是一种音效而已,从输出混音器资源对象接口中可以获取,在不需要这种音效的时候可以不用获取该接口来更改混音器资源里面的属性
  • SLPlayItf:播放器状态控制接口(从播放器或采样器资源里面获取),可以通过该接口来对播放器对象的状态进行控制,函数SetPlayState来对播放器的播放,暂停,停止进行控制
  • SLAndroidSimpleBufferQueueItf:播放器队列接口(从播放器资源或采集器里面获取),通过该接口为播放器对象这组资源进行数据队列回调的设置,以及开启队列的循环,提供了一种音频数据的缓存队列的方式
  • SLVolumeItf :播放器音量接口(从播放器资源中获取),通过该接口可以对播放器的输出音量进行控制,至于输入音量没有测试过

2、openSL步骤:

  • 创建并实现引擎:创建引擎是为了孵化其他接口
  • 创建并实现输出混音器:通过引擎孵化,输出混音器除了能够给增加音效,还实现了将声音输出特定硬件里面并发出声音
  • 设置数据输入:数据源(Data source)是媒体对象的输入参数(代表着输入源的信息,即数据从哪儿来、输入的数据参数是怎样的)。在作为播放器的时候,输入参数一般就是被播放的数据源,例如uri或者本地文件或者队列缓存
  • 设置数据输出:数据接收器(Data sink)是媒体对象的输出参数,即数据输出到哪儿、以什么样的参数来输出。在作为录音器的时候,输出参数一般就是采集出来的指定数据,例如采集出来的pcm数据
  • 创建播放器对象或采集器对象:通过引擎孵化,如果进行原始音频流的播放则孵化播放器对象资源,如果是采样声音则孵化采样器对象
  • 获取缓存队列接口并设置回调:通过播放器或者采集器的对象资源获取SLAndroidSimpleBufferQueueItf接口,并设置回调函数,在回调函数中可以进行音频数据流的读(读取本地文件送给播放器进行播放)或者写(从采集器获取数据并写如本地文件)
  • 获取播放器状态接口并设置播放状态:通过播放器或者采集器的对象资源获取SLPlayItf接口,播放器通过SetPlayState函数来设置播放状态,采集器通过SetRecordState设置采集状态
  • 通过缓存队列接口设置开启队列循环:调用Enqueue函数开始进行回调函数的调用,这个至关重要,回调函数不回调,就无法将数据送到播放器里面

3、输入输出:

数据源(Data source)是媒体对象的输入参数,指定媒体对象将从何处接收特定类型的数据(例如采样的音频或MIDI数据)

typedef struct SLDataSource_ {void *pLocator;void *pFormat;
} SLDataSource;

数据接收器(Data sink)是媒体对象的输入参数,指定媒体对象将发送特定类型数据的位置

typedef struct SLDataSink_ {void *pLocator;void *pFormat;
} SLDataSink;

OpenSL ES 里面,这两个结构体均是作为创建 Media Object 对象时的参数而存在的,Data source 代表着输入源的信息,即数据从哪儿来、输入的数据参数是怎样的;而 Data Sink 代表着输出的信息,即数据输出到哪儿、以什么样的参数来输出。其中SLDataSource和SLDataSink结构完全一样,pLocator表示流的位置(可以是本地文件,可以是URI,可以是缓存队列,还可以指定输出混音器),pFormat表示流的数据格式。下面我们可以看看OpenSL在不同场景下是如何配置输入输出源的呢:

  • 创建队列方式播放器:输入源设置为队列缓存模式并可以指定队列缓存大小,输出指定到了混响器(暂时理解成控制喇叭或者音响耳机的软件模块)
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, //数据源从队列缓存方式中获取4};         //指定队列缓存大小
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};
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};    //输出混音器
SLDataSink audioSnk = {&loc_outmix, NULL};
  • 创建URI播放器:输入源设置为URI模式并指定了uri地址,这里需要指定为SL_DATAFORMAT_MIME格式(只能对URI数据定位器使用MIME数据格式,而且只能对音频播放器使用。您不能将此数据格式用于音频录制器),输出同上
SLDataLocator_URI loc_uri = {SL_DATALOCATOR_URI, //指定输入源为URI方式(SLchar *) utf8};    //uri字符串
SLDataFormat_MIME format_mime = {SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_UNSPECIFIED};
SLDataSource audioSrc = {&loc_uri, &format_mime};
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
SLDataSink audioSnk = {&loc_outmix, NULL};
  • 创建文件流播放器:输入源设置为文件模式,输入源的数据格式同上,个人理解这样指定实现了OpenSL根据输入源URI或者文件的来进行自适应的数据格式
SLDataLocator_AndroidFD loc_fd = {SL_DATALOCATOR_ANDROIDFD, //输入流为文件fd,                       //文件描述符start,                    //输入流的起始位置length};      //输入流的长度 从改文件中获取指定位置指定长度
SLDataFormat_MIME format_mime = {SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_UNSPECIFIED};
SLDataSource audioSrc = {&loc_fd, &format_mime};
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
SLDataSink audioSnk = {&loc_outmix, NULL};
  • 创建录音器:输入源设置为SL_DATALOCATOR_IODEVICE,个人理解这里的配置是将系统的默认音频输入的IO设备作为OpenSL输入源,经过OpenSL的处理转换成数字信号,并放在缓存队列里面,我们可以通过设置缓存队列的方式将这些数据提取出来
SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT,SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};
SLDataSource audioSrc = {&loc_dev, NULL};
SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 4};
SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, 1, SL_SAMPLINGRATE_16,SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN};
SLDataSink audioSnk = {&loc_bq, &format_pcm};    //输出指定为缓存方式(用过缓存队列回调获取采集出来的数据)

二、OpenSL示例:

OpenSLDemo

1、OpenSL基类的封装:

下面代码抽取了OpenSL公共部分进行封装,并实现了引擎的创建和OpenSL启用的常规步骤

#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
class SLBase{
public:SLBase();virtual ~SLBase();void Start();void Stop();
protected://引擎SLObjectItf engineObject = nullptr;SLEngineItf engineEngine = nullptr;bool CreateEngine();                    //创建引擎void ReleaseEngine();                   //释放引擎//输入输出SLDataSource dataSource;SLDataSink dataSink;virtual bool SetDataSource()=0;         //设置输入源virtual bool SetDataSink()=0;           //设置输出源//播放器或者录音器virtual bool CreateFeature()=0;         //创建XXX器virtual void ReleaseFeature()=0;        //释放XXX器virtual bool SetStateRuning()=0;        //设置XXX器运行状态virtual bool SetStateStoping()=0;       //设置XXX器停止状态//队列bool isQueueLooping = false;SLAndroidSimpleBufferQueueItf queueItf;slAndroidSimpleBufferQueueCallback queueCallBack;void SetQueueState(bool isLoop);
public:void SetQueueCallBack(slAndroidSimpleBufferQueueCallback callback);bool SendQueueLoop(const void *pBuffer,SLuint32 size);bool IsQueueLooping() const { return isQueueLooping; };bool IsQueueSelf(SLAndroidSimpleBufferQueueItf queue);
};
SLBase::SLBase() {}
SLBase::~SLBase() {ReleaseEngine();
}
//设置回调方法
void SLBase::SetQueueCallBack(slAndroidSimpleBufferQueueCallback callback) {queueCallBack = callback;
}
/*** 设置队列状态* @param isLoop true正在轮询状态*/
void SLBase::SetQueueState(bool isLoop) {isQueueLooping = isLoop;
}
// 像队列发送数据并轮询
bool SLBase::SendQueueLoop(const void *pBuffer, SLuint32 size) {if(!isQueueLooping)return false;SLresult result = (*queueItf)->Enqueue(queueItf, pBuffer,size);if(result!=SL_RESULT_SUCCESS) return false;return true;
}
/*** 检查回调函数回来的队列是否是自己* @param queue 因为SLAndroidSimpleBufferQueueItf已经实现了==运算符重载,因此不需要来比较是否为同一个对象(即不需要比较他们的地址是否相等,已经验证他们的地址不相等)* @return*/
bool SLBase::IsQueueSelf(SLAndroidSimpleBufferQueueItf queue) {if(queue == queueItf)return true;elsereturn false;
}
bool SLBase::CreateEngine() {SLresult result;result = slCreateEngine(&engineObject, 0, nullptr, 0, nullptr, nullptr);if(result != SL_RESULT_SUCCESS)return false;result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);if(result != SL_RESULT_SUCCESS)return false;result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);if(result != SL_RESULT_SUCCESS) return false;return true;
}
void SLBase::ReleaseEngine() {engineEngine = nullptr;if (engineObject != nullptr) {(*engineObject)->Destroy(engineObject);engineObject = nullptr;}
}
void SLBase::Start() {//创建引擎bool isSucced = CreateEngine();if(!isSucced)return;//设置输入源isSucced = SetDataSource();if(!isSucced)return;//设置输出源isSucced = SetDataSink();if(!isSucced)return;//创建播放器或者录音器isSucced = CreateFeature();if(!isSucced)return;//设置运行状态isSucced = SetStateRuning();if(!isSucced)return;//开启轮询 注意:最后需要手动调用SendQueueLoop方法来开启轮询SetQueueState(true);
}
void SLBase::Stop() {LOGE("SLBase-Stop");//停止轮询SetQueueState(false);LOGE("SLBase-停止轮询");SetStateStoping();LOGE("SLBase-SetStateStoping");ReleaseFeature();LOGE("SLBase-ReleaseFeature");ReleaseEngine();LOGE("SLBase-ReleaseEngine");
}

2、OpenSL录音器实现:

下面代码封装了录音器并继承SLBase,主要封装了录音器的创建过程以及录音器的输入输出设置

class SLRecorder : public SLBase{
public:SLRecorder();virtual ~SLRecorder();
protected:SLObjectItf recorderObject;SLRecordItf recorderItf;virtual bool SetDataSource();           //设置输入源virtual bool SetDataSink();             //设置输出源virtual bool CreateFeature();           //创建XXX器virtual void ReleaseFeature();          //释放XXX器virtual bool SetStateRuning();          //设置XXX器运行状态virtual bool SetStateStoping();         //设置XXX器停止状态
};
SLRecorder::SLRecorder() : SLBase(){}
SLRecorder::~SLRecorder() {ReleaseFeature();
}
bool SLRecorder::SetDataSource() {static SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE,SL_IODEVICE_AUDIOINPUT,SL_DEFAULTDEVICEID_AUDIOINPUT,nullptr};//注意loc_dev不用static修饰在该函数结束就会被释放dataSource = {&loc_dev, nullptr};return true;
}
bool SLRecorder::SetDataSink() {static SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 10};static SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM,2,SL_SAMPLINGRATE_44_1,SL_PCMSAMPLEFORMAT_FIXED_16,SL_PCMSAMPLEFORMAT_FIXED_16,SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT,//声道SL_BYTEORDER_LITTLEENDIAN};dataSink = {&loc_bq, &format_pcm};return true;
}
//创建录音器
bool SLRecorder::CreateFeature() {SLresult result;const int length = 1;const SLInterfaceID ids[length] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};const SLboolean req[length] = {SL_BOOLEAN_TRUE};result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &dataSource,&dataSink, length, ids, req);if(SL_RESULT_SUCCESS != result)return false;result = (*recorderObject)->Realize(recorderObject,SL_BOOLEAN_FALSE);if(SL_RESULT_SUCCESS != result)return false;result = (*recorderObject)->GetInterface(recorderObject,SL_IID_RECORD,&recorderItf);if(SL_RESULT_SUCCESS != result)return false;result = (*recorderObject)->GetInterface(recorderObject,SL_IID_ANDROIDSIMPLEBUFFERQUEUE,&queueItf);if(SL_RESULT_SUCCESS != result) return false;result = (*queueItf)->RegisterCallback(queueItf,queueCallBack, nullptr);if(SL_RESULT_SUCCESS != result)return false;return true;
}
//释放录音器
void SLRecorder::ReleaseFeature() {if (recorderObject != nullptr) {(*recorderObject)->Destroy(recorderObject);recorderObject = nullptr;queueItf = nullptr;}
}
bool SLRecorder::SetStateRuning() {SLresult result = (*recorderItf)->SetRecordState(recorderItf,SL_RECORDSTATE_RECORDING);if(result!=SL_RESULT_SUCCESS)return false;return true;
}
bool SLRecorder::SetStateStoping() {SLresult result = (*recorderItf)->SetRecordState(recorderItf,SL_RECORDSTATE_STOPPED);if(result!=SL_RESULT_SUCCESS)return false;return true;
}

3、OpenSL播放器实现:

下面代码封装了播放并继承SLBase,主要封装了播放的创建过程以及播放器的输入输出设置

class SLPlayer:public SLBase{
public:SLPlayer();virtual ~SLPlayer();
protected://输出混音器SLObjectItf outMixObject;//播放器SLObjectItf playerObject;SLPlayItf playerItf;virtual bool SetDataSource();           //设置输入源virtual bool SetDataSink();             //设置输出源virtual bool CreateFeature();           //创建XXX器virtual void ReleaseFeature();          //释放XXX器virtual bool SetStateRuning();          //设置XXX器运行状态virtual bool SetStateStoping();         //设置XXX器停止状态
};
SLPlayer::SLPlayer() : SLBase(){}
SLPlayer::~SLPlayer() {ReleaseFeature();
}
bool SLPlayer::SetDataSource() {static SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 4};static SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM,2,SL_SAMPLINGRATE_44_1,SL_PCMSAMPLEFORMAT_FIXED_16,SL_PCMSAMPLEFORMAT_FIXED_16,SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT,//声道SL_BYTEORDER_LITTLEENDIAN};//注意loc_bufq和format_pcm不用static修饰在该函数结束就会被释放dataSource = {&loc_bufq, &format_pcm};return true;
}
bool SLPlayer::SetDataSink() {SLresult result;const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB};const SLboolean req[1] = {SL_BOOLEAN_FALSE};result = (*engineEngine)->CreateOutputMix(engineEngine, &outMixObject, 1, ids, req);if(SL_RESULT_SUCCESS != result) return false;result = (*outMixObject)->Realize(outMixObject, SL_BOOLEAN_FALSE);if(SL_RESULT_SUCCESS != result) return false;static SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outMixObject};//注意loc_outmix不用static修饰在该函数结束就会被释放dataSink = {&loc_outmix, nullptr};return true;
}
//创建缓存播放器
bool SLPlayer::CreateFeature() {SLresult result;const int length = 2;const SLInterfaceID ids[length] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};const SLboolean req[length] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};result = (*engineEngine)->CreateAudioPlayer(engineEngine, &playerObject, &dataSource, &dataSink,length, ids, req);if (SL_RESULT_SUCCESS != result)return false;result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);if (SL_RESULT_SUCCESS != result) return false;result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerItf);if (SL_RESULT_SUCCESS != result) return false;result = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &queueItf);if (SL_RESULT_SUCCESS != result) return false;if (queueCallBack == nullptr)return true;result = (*queueItf)->RegisterCallback(queueItf, queueCallBack, nullptr);if (SL_RESULT_SUCCESS != result)return false;return true;
}
void SLPlayer::ReleaseFeature() {if(playerObject != nullptr) {(*playerObject)->Destroy(playerObject);playerObject = nullptr;playerItf = nullptr;queueItf = nullptr;}if(outMixObject != nullptr){(*outMixObject)->Destroy(outMixObject);outMixObject = nullptr;}
}
bool SLPlayer::SetStateRuning() {SLresult result = (*playerItf)->SetPlayState(playerItf,SL_PLAYSTATE_PLAYING);if(result!=SL_RESULT_SUCCESS) return false;return true;
}
bool SLPlayer::SetStateStoping() {SLresult result = (*playerItf)->SetPlayState(playerItf,SL_PLAYSTATE_STOPPED);if(result!=SL_RESULT_SUCCESS)return false;return true;
}

4、JNI调用OpenSL的封装:

下面代码是JNI通过调用上面封装的类,通过基类SLBase指针多态的方式操作OpenSL的播放器和录音器功能,并将回调函数分离出来外界进行操作,除此之外进行采样和播放的文件处理

#define BUF_SIZE (1024*4)
#define DEF_FILE_PATH "/storage/emulated/0/SHEN_RES/aqjz.pcm"
#define TEMP_FILE_PATH "/storage/emulated/0/SHEN_RES/temp.pcm"
FILE *readFp = nullptr;     //播放器用到的读取文件
FILE *writeFp = nullptr;    //录音器用到的写入文件
char *readBuf = nullptr;    //读取缓存
char *writeBuf = nullptr;   //写入缓存
SLBase *player;             //播放器
SLBase *recorder;           //录音器
void initPlayerResource(){if(readFp == nullptr) readFp = fopen(TEMP_FILE_PATH, "rb"); //华为if(readBuf == nullptr) readBuf = new char[BUF_SIZE];
}
void initRecorderResource(){if(!writeFp) writeFp = fopen(TEMP_FILE_PATH, "wb+"); //华为if(writeBuf == nullptr) writeBuf = new char[BUF_SIZE];
}
void freePlayerResource(){if(readFp != nullptr) {fclose(readFp);readFp = nullptr;}if(readBuf != nullptr){delete readBuf;readBuf = nullptr;}
}
void freeRecorderResource(){if(writeFp != nullptr) {fclose(writeFp);writeFp = nullptr;}if(writeBuf != nullptr){delete writeBuf;writeBuf = nullptr;}
}
//播放器的回调函数
void PlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {if(readFp == nullptr) return;if (player == nullptr) return;if(!player->IsQueueSelf(bq)) return;if(player->IsQueueLooping()){int len = fread(readBuf,1,BUF_SIZE,readFp);LOGD("PlayerCallback:len=%d",len);if (len > 0) player->SendQueueLoop(readBuf,len);}
}
//录音器的回调函数
void RecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {if(!writeFp)return;if (recorder == nullptr)return;if(!recorder->IsQueueSelf(bq)) return;if(recorder->IsQueueLooping()){fwrite(writeBuf,1,BUF_SIZE,writeFp);fflush(writeFp);recorder->SendQueueLoop(writeBuf,BUF_SIZE);}
}
extern "C" JNIEXPORT void JNICALL Java_com_shen_opensles_MainActivity_startPlay(JNIEnv *env, jobject instance) {initPlayerResource();player = new SLPlayer();player->SetQueueCallBack(PlayerCallback);player->Start();player->SendQueueLoop("",1);    //开启轮询
}
extern "C" JNIEXPORT void JNICALL Java_com_shen_opensles_MainActivity_stopPlay(JNIEnv *env, jobject instance) {player->Stop();freePlayerResource();delete player;
}
extern "C" JNIEXPORT void JNICALL Java_com_shen_opensles_MainActivity_startRecord(JNIEnv *env, jobject instance) {initRecorderResource();recorder = new SLRecorder();recorder->SetQueueCallBack(RecorderCallback);recorder->Start();recorder->SendQueueLoop(writeBuf,BUF_SIZE); //因为开启轮询的方式依赖外部缓存,因此提供了一个函数让外部调用
}
extern "C" JNIEXPORT void JNICALL Java_com_shen_opensles_MainActivity_stopRecord(JNIEnv *env, jobject instance) {recorder->Stop();freeRecorderResource();delete recorder;
}

5、OpenSL的音量控制:

//获取出来的最大音量Level为0
result = (*bqPlayerVolume)->GetMaxVolumeLevel(bqPlayerVolume,&vol);
//根据官方示例的音量计算公式
vol = (100-volue) * (-50);
//设置音量,范围为负数到0
result = (*bqPlayerVolume)->SetVolumeLevel(bqPlayerVolume,vol);
//经过测试上面公式vol=100,SetLevel方法参数为0,这个时候音量最大
//Vol=0,SetLevel方法参数为-5000,这个时候还是有音量,但不知道是不是最小

6、两种buffer队列ID:

在写上面demo的时候发现,在录音器获取队列接口的时候总是失败,后面对比官方示例发现,从播放器录音器中获取队列接口的时候,传的SL_ID不一样如下:

//从录音器中获取队列接口
(*recordObject)->GetInterface(recordObject,SL_IID_ANDROIDSIMPLEBUFFERQUEUE,&queueItf);
//从播放器中获取队列接口
(*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &queueItf);//OpenSLES.h定义SL_IID_BUFFERQUEUE
extern SL_API const SLInterfaceID SL_IID_BUFFERQUEUE;
//OpenSLES_Android.h定义SL_IID_ANDROIDSIMPLEBUFFERQUEUE
extern SL_API const SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
//xref: /frameworks/wilhelm/src/sl_iid.cpp定义如下
const SLInterfaceID SL_IID_BUFFERQUEUE = &SL_IID_array[MPH_BUFFERQUEUE];
const SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE = &SL_IID_array[MPH_ANDROIDSIMPLEBUFFERQUEUE];
继续跟踪源码发现在xref: /frameworks/wilhelm/src/OpenSLES_IID.cpp文件中定义了数组SL_IID_array,关于他们的定义如下:

从上面发现,他们的值不一样,估计跟安卓底层有关系吧,官方也没有给出具体的解释,暂且作罢

参考链接:

音频数字信号详解

android平台OpenSL ES播放PCM数据

Android的声音编程--使用OpenSL ES Audio

 


http://chatgpt.dhexx.cn/article/jLighSSt.shtml

相关文章

最简单的OpenSL播放PCM实时音频

这里是c语言写的给android用的&#xff0c;可以拿到其他平台使用。既然是最简单的&#xff0c;肯定使用起来就是超级简单如回调方法就一句代码。这里简单说一下使用要注意的地方: 1.如果想要使用opensl的一些功能如音量控制&#xff1a; 只是这样是不可以的&#xff0c;拿到…

Android12之OpenSL ES通路hidl flag与hal flag转换原理(十八)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药. 更多原创,欢迎关注:Android…

分析OpenSL回声Demo

分析OpenSL回声Demo 前言 回声App是google的一个使用openSL的示例。 代码地址: https://github.com/android/ndk-samples/tree/main/audio-echo 通过代码可以学习到如何简单使用OpenSL采集音频&#xff0c;播放音频。 Demo的设计图 流程 App的大致流程为&#xff1a; 使用…

Android音视频【十三】OpenSL ES介绍基于OpenSL ES实现音频采集

人间观察 勿再别人的心中修行自己&#xff0c; 勿再自己的心中强求别人。 前言 最近写文章有点偷懒了&#xff0c;离上次写文章大概一个月了。 一般Android音频的采集在java层使用AudioRecord类进行采集。 但是为什么要学OpenSL呢?除了C/C的性能优势(不过其实java的效率也不…

Harmony Native开发-我的OpenSL ES录音机

零、写在前面 最早我是在Android上开发的OpenSL ES。但最近看了下鸿蒙的文档&#xff0c;发现它的底层库也支持OpenSL ES&#xff0c;这我的兴致就来了。简单了解了一下鸿蒙的Native开发&#xff0c;就着手开发起来。移植过程中发现其实对Android程序员还是相当友好的&#xf…

Android中opensl架构,Android OpenSL ES详解

简介 NDK开发OpenSL ES跨平台高效音频解决方案.png OpenSL ES全称为Open Sound Library for Embedded Systems,即嵌入式音频加速标准。OpenSL ES是无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速 API。它为嵌入式移动多媒体设备上的本地 应用程序开发者提供了标准化…

音视频学习 AudioTrack、OpenSL ES 音频渲染

前言 在讲解音频渲染之前&#xff0c;需要对音频的基础知识有所了解&#xff0c;所以该篇分为基础概念和AudioTrack 以及 OpenSL ES Demo 实例讲解&#xff0c;这样有助于更好的理解 Android 中音频渲染。 音频的基础概念涉及的知识点比较多&#xff0c;该篇文章的上半部分会…

OpenSL ES技术分析

背景简介 OpenSL ES是一种针对嵌入式系统特别优化过的硬件音频加速API&#xff0c;无授权费并且可以跨平台使用。它提供的高性能、标准化、低延迟的特性实现为嵌入式媒体开发提供了标准&#xff0c;嵌入式开发者在开发本地音频应用也将变得更为简便&#xff0c;利用该API能够实…

使用MediaCodec+OpenSL编写简单的音频播放器

使用MediaCodecOpenSL编写简单的音频播放器 前言 通过MediaCodec Native API 和OpenSL编写一个简单的音频播放器。可以解码并播放一个mp3文件. 流程 初始化 使用AMediaExtractor解析Mp3文件&#xff0c;它可以得到音频文件的格式、以及帧&#xff08;未解码&#xff09;。…

使用Android高性能音频--OpenSL ES和AAudio

AAudio的概念介绍: AAudio 是作为 OpenSL ES 库的轻量级原生 Android 替代项而开发。 与 OpenSL ES 相比&#xff0c;AAudio API 不仅较小&#xff0c;而且容易使用。 AAudio 是在 Android O 版本中引入的全新 Android C API。 因此 API 是专为需要低延迟的高性能音频应用而设…

opensl学习笔记

这个是看opensl文档看到的一个简单的使用示例图&#xff0c;单纯看代码的话很难理解opensl播放和采集的原理&#xff0c;可以结合图来理解。使用Audio Player来播放音频&#xff0c;DataSource类型为SLDataSource&#xff0c;例如安卓设备就是SL_DATALOCATOR_ANDROIDSIMPLEBUFF…

使用OpenSL直接播放mp3

使用OpenSL直接播放mp3 前言 通过使用OpenSL来播放一个mp3文件来学习openSL的使用方式。 设计 在android平台播放mp3方式有多种方式入使用MediaPlayer、AudioTrack、OpenSL、oboe等。根据使用MediaPlayer,AudioTrack的经验一个播放器需要有的基础功能有加载数据、开始、暂停…

音视频开发之旅(36) -FFmpeg +OpenSL ES实现音频解码和播放

目录 OpenSL ES基本介绍OpenSL ES播放音频流程代码实现遇到的问题资料收获 上一篇我们通过AudioTrack实现了FFmpeg解码后的PCM音频数据的播放&#xff0c;在Android上还有一种播放音频的方式即OpenSL ES, 什么是OpenSL ES&#xff0c;这个我们平时接触的很少&#xff0c;原因…

Android OpenSL ES 开发:Android OpenSL 介绍和开发流程说明

一、Android OpenSL ES 介绍 OpenSL ES (Open Sound Library for Embedded Systems)是无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速API。它为嵌入式移动多媒体设备上的本地应用程序开发者提供标准化, 高性能,低响应时间的音频功能实现方法&#xff0c;并实现软/硬件…

Xcode7 网络请求报错:The resource could not be loaded because the App Transport Security policy requir...

Google后查证&#xff0c;iOS9引入了新特性App Transport Security (ATS)。详情&#xff1a;App Transport Security (ATS)新特性要求App内访问的网络必须使用HTTPS协议。但是现在公司的项目使用的是HTTP协议&#xff0c;使用私有加密方式保证数据安全。现在也不能马上改成HTTP…

【用户自定义气象站】

自定义气象站系统由数据采集器、传感器、总线模块、网络模块、供电模块等组成&#xff0c;可实现野外无人看守的情况下长期监测。用户可根据自己的需求&#xff0c;自定义测量指标&#xff0c;以达到观测目的。 自定义气象站测量指标 自定义气象站测量参数指标 总辐射、光合…

自动气象站设备的防雷要点

自动气象站设备&#xff0c;其工作场所几乎都是在户外&#xff0c;雨打风吹、电闪雷鸣、日晒雨淋&#xff0c;由于是雷雨天气&#xff0c;气象站也要连续工作。如何科学防雷&#xff0c;直接影响到气象站的运行与使用寿命。 自动气象站设备的工作要点是获取气象环境参数&#x…

12,桥接模式-露娜的召唤师技能

一,前言 7种结构型设计模式:桥接模式,适配器模式,装饰模式,组合模式,享元模式,外观模式,代理模式上篇我们说了装饰模式:动态地将责任附加到对象上,在不修改任何底层代码的情况下,为对象赋予新的职责开发中,我们经常会遇到一个类有两个或两个以上的维度经常在变化 如果我们使用…

浮标水质监测站是什么

浮标水质监测站是设立在河流、湖泊、水库、近岸海域等流域内进行现场水质自动监测的监测仪器&#xff0c;是以水质监测仪为核心&#xff0c;运用传感器技术&#xff0c;结合浮标体、电源供电系统、数据传输设备组成的放置于水域内的小型水质监测系统。用于连续自动监测被测水体…

【赛纳斯】EC Raman电化学拉曼光谱检测系统推动科研新突破

【前言】   近日,Angew在线发表了厦门大学李剑锋教授团队在设计用于氧还原反应的先进材料及改进催化剂的设计最 新综述文章。该论文综述了双金属纳米催化剂有序度对氧还原反应的影响。论文第 一作者为&#xff1a;Heng-Quan Chen,Huajie Ze,Mu-Fei Yue,论文共同通讯作者为&am…