OpenSL ES音频库学习
OpenSL ES音频库学习
简介
什么是OpenSL ES? openSL ES是一个专用于嵌入式系统的音频库,可以提供对音频的播放和录制等相关功能,在Android上Aduio Recoder都是基于此库实现的,同时,我们也可以在Android的JNI里面使用此库进行音频开发,官方介绍请点击
使用方式
OpenSL ES几乎都是通过一个Object一个Interface成对来获取一项功能,比如OpenSL ES的全局引擎engineObject和engineInterface:
SLObjectItf engineObject; //引擎始祖对象
SLEngineItf engineInterface; //引擎接口
slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
(*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
(*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineInterface);
内部大致原理
内部状态机制
从上一步得知所有的功能都是先创建Object,在获取其interface,其内部大致有这么一个状态机制
- 创建Object时,其处于未实现UNREALIZED 状态,内部也没有为其分配任何资源
- Realize激活后,才能通过Object获取其功能Interface,此时处于REALIZED状态
- 当音频被抢占或出现错误,Object处于SUSPENDED暂停状态,如果想恢复REALIZED需要调用Object的Resume方法
- 当我们把Object销毁后,就进入UNREALIZED状态了,重新使用需要在REALIZED才行
下图是参考网图:
IO回调机制
使用OpenSL ES进行音频播放时,当缓冲区数据没有填充满,会一直触发回调函数,直到缓冲区填满后停止,当缓冲区数据消耗完后,再近些下一轮的回调触发
部分功能介绍
录音Recorder
录音重点主要集中在初始化配置方面:
- 配置输入和输出
- 配置缓冲区IO回调
- 创建RecorderObject和RecorderInterface
输入和输出
SLDataSource和SLDataSink
typedef struct SLDataSource_ {
void *pLocator; //输入数据类别
void *pFormat; //输入数据格式
} SLDataSource;
typedef struct SLDataSink_ {
void *pLocator;
void *pFormat;
} SLDataSink;
其中pLocator的类型可以是:
SLDataLocator_Address
SLDataLocator_BufferQueue
SLDataLocator_IODevice
SLDataLocator_MIDIBufferQueue
SLDataLocator_URI
针对录音的数据来源我们可以选择IODevice,以下是对IODevice的配置:
//录音源source
slHelper->ioDevice.locatorType = SL_DATALOCATOR_IODEVICE; //源头是io设备
slHelper->ioDevice.deviceType = SL_IODEVICE_AUDIOINPUT; //音频输入
slHelper->ioDevice.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
slHelper->ioDevice.device = NULL; //device ID生效的前提必须device NULL
配置录音的输出,我们选择SLDataLocator_BufferQueue类型,输出到缓冲区,还需要配置输出的数据给format
//输出
queue.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; //输出到缓冲队列
queue.numBuffers = 2; //2个缓冲队列
pcmFormat.formatType = SL_DATAFORMAT_PCM; //录音数据格式
pcmFormat.numChannels = channels;
pcmFormat.samplesPerSec = sampleRate;
pcmFormat.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; //每个采样点bit
pcmFormat.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
pcmFormat.channelMask = getChannelMask(channels); //根据声道数确定掩码
pcmFormat.endianness = SL_BYTEORDER_LITTLEENDIAN; //字节小端模式
sink.pLocator = &(queue);
sink.pFormat = &(pcmFormat);
最后,就可以创建Recoder开始播放了:
///Recorder/
SLInterfaceID id[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
SLboolean required[] = {SL_BOOLEAN_TRUE};
(*engineInterface)->CreateAudioRecorder(engineInterface, &recorderObject
, &source, &sink, 1, id, required);
(*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE);
//Register
(*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&(androidBufferQueueItf));
(*androidBufferQueueItf)->RegisterCallback(androidBufferQueueItf, openSLCallBack, NULL);
//start recorder
(*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderInterface);
(*recorderInterface)->SetRecordState(recorderInterface, SL_RECORDSTATE_RECORDING);
ok,以上就是楼主学习过程中的一点小小心得,如有不正,请指正;
以下是我的学习demo
音量调节
也是调用openSL ES的音量接口,设置分贝值即可,重要的是分贝值的计算。
SLmillibel level = getAmplificationLevel((leftVolume + rightVolume) / 2);
SLresult lresult = (*slVolumeItf)->SetVolumeLevel(slVolumeItf, level);
if (lresult != SL_RESULT_SUCCESS) {
LOGE("slVolumeItf->SetVolumeLevel failed %d\n", (int)lresult);
}
SLmillibel getAmplificationLevel(float volumeLevel) {
if(volumeLevel < 0.0000001){
return SL_MILLIBEL_MIN;
}
//分贝计算公式
SLmillibel mb = lround(20f * log10f(volumeLevel));
if(mb < SL_MILLIBEL_MIN){
mb = SL_MILLIBEL_MIN;
}
return mb;
}
主要理解分贝(声压级)计算公式:
上诉表达式中,推导过程不做解释,Prms表示实际测的声压级,Pref是参考的声压级,由于人耳能听到最微小的声压级为0.00002Pa,所以默认是Pref一般采用这个值作为参考值计算出分贝,而不同手机厂商精度可能不一样,一个粗糙的计算方法是,环境静音时手机测量出来的值(MediaRecorder.getMaxAmplitude)作为Pref,然后在计算
部分API理解
- SLresult (*CreateAudioPlayer) (
SLEngineItf self,
SLObjectItf * pPlayer,
SLDataSource *pAudioSrc,
SLDataSink *pAudioSnk,
SLuint32 numInterfaces,
const SLInterfaceID * pInterfaceIds,
const SLboolean * pInterfaceRequired
); - SLresult (*CreateAudioRecorder) (
SLEngineItf self,
SLObjectItf * pRecorder,
SLDataSource *pAudioSrc,
SLDataSink *pAudioSnk,
SLuint32 numInterfaces,
const SLInterfaceID * pInterfaceIds,
const SLboolean * pInterfaceRequired
);
以上两个函数用法类似,前4个参数不多做解释,主要是后三个参数,标明创建播放器和录音器所具有的属性,
numInterfaces:属性的数量
pInterfaceIds:属性特性数组,数组长度和属性数量相同
pInterfaceRequired:上面的属性启动或者不启动,数组长度和属性数量相同
以播放器为例,播放器来源有bufferQueue、混音器还有音量调节,那么你应该做如下配置:
const SLInterfaceID ids[3] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME, SL_IID_PLAY};
const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
result = (*slEngine)->CreateAudioPlayer(slEngine, &slPlayerObject, &slDataSource,
&audioSink, 3, ids, req);
参考的链接:
https://juejin.im/post/5bda5ed85188257f3e09d6f5
https://zhuanlan.zhihu.com/p/20865418
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】