分析OpenSL回声Demo

article/2025/7/4 2:12:41

分析OpenSL回声Demo

前言

回声App是google的一个使用openSL的示例。
代码地址:
https://github.com/android/ndk-samples/tree/main/audio-echo
通过代码可以学习到如何简单使用OpenSL采集音频,播放音频。

Demo的设计图

在这里插入图片描述

流程

App的大致流程为:

  1. 使用slCreateEngine创建出一个SLObjectItf然后通过调用它的GetInterace方法获取一个SLEngineItf。
// 1. 创建slEngineObj
result = slCreateEngine(&engine.slEngineObj_, 0, NULL, 0, NULL, NULL);
SLASSERT(result);
// 2. 初始化 slEngineObj
result = (*engine.slEngineObj_)->Realize(engine.slEngineObj_, SL_BOOLEAN_FALSE);
SLASSERT(result);
// 3. 获取 SLEngineItf
result = (*engine.slEngineObj_)->GetInterface(engine.slEngineObj_, SL_IID_ENGINE, &engine.slEngineItf_);
SLASSERT(result);
  1. 初始化两个队列recQueue和freeQueue。RecQueue中的用来存储录制的数据并提供数据给AudioPlayer播放。FreeQueue则回收AudioPlayer播放完的buffer。
            recQueue----载有数据的buffer-------->    
录制                              播放<--------空buff-------------   freeQueue  
  1. 使用SLEngineItf创建AudioPlayer,创建AudioPlayer的时候需要指定输入以及输出。对于AudioPlayer来讲,输出为Outputmix,输入为内存。
  • 创建SLDataLocator_AndroidSimpleBufferQueue作为AudioPlayer的输入.
  • 创建SLDataLocator_OutputMix作为AudioPlayer的输出。
  • 以上的输入输出类型需要使用SLDataSource/SLDataSink进行包装然后在创建AudioPlayer的时候传入
  • 设置callback: slAndroidSimpleBufferQueueCallback 应用程序将在callback中为SLDataLocator_AndroidSimpleBufferQueue提供数据
// 创建OuputMix作为输出
(*slEngine)->CreateOutputMix(slEngine, &outputMixObjectItf_, 0, NULL, NULL);
result = (*outputMixObjectItf_)->Realize(outputMixObjectItf_, SL_BOOLEAN_FALSE);
// 输入使用BufferQueue作为输入
SLDataLocator_AndroidSimpleBufferQueue locBufQ = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, DEVICE_SHADOW_BUFFER_QUEUE_LEN};
SLAndroidDataFormat_PCM_EX_ format_pcm;
ConvertToSLSampleFormat(&format_pcm, &sampleInfo_);
SLDataSource audioSrc = {&locBufQ, &format_pcm};SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObjectItf_};
// 包装:OutputMix
SLDataSink audioSink = {&loc_outmix, NULL};
SLInterfaceID ids[2] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
result = (*slEngine)->CreateAudioPlayer(slEngine, &playerObjectItf_, &audioSrc,&audioSink, sizeof(ids) / sizeof(ids[0]), ids, req);
// 初始化player
result = (*playerObjectItf_)->Realize(playerObjectItf_, SL_BOOLEAN_FALSE);
result = (*playerObjectItf_)->GetInterface(playerObjectItf_, SL_IID_PLAY,&playItf_);
result = (*playerObjectItf_)->GetInterface(playerObjectItf_, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,  &playBufferQueueItf_);
result = (*playBufferQueueItf_)->RegisterCallback(playBufferQueueItf_, bqPlayerCallback, this);
  1. 使用SLengineItf创建AudioRecorder,同创建AudioPlayer一样需要指定输入和输出
  • 创建SLDataLocator_IODevice作为AudioRecorder的输入。
  • 创建SLDataLocator_AndroidSimpleBufferQueue作为AudioRecorder的输出。
  • 同样需要使用SLDataSource/SLDataSink对上面的输入和输出进行包装。
  • 为SLDataLocator_AndroidSimpleBufferQueue设置callback,在callback中进行读取录制好的数据以及提供空buffer给slAndroidSimpleBufferQueueCallback。
// 输入,对应IO设备
SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE,SL_IODEVICE_AUDIOINPUT,SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};
SLDataSource audioSrc = {&loc_dev, nullptr};
// 输出,对应内存buffer
SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, DEVICE_SHADOW_BUFFER_QUEUE_LEN};
SLDataSink audioSink = {&loc_bq, &format_pcm};
const SLInterfaceID id[2] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION};
const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
// 创建AudioRecorder,audioSrc为输入,audioSink为输出
result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recObjectItf_, &audioSrc,&audioSink,sizeof(id) / sizeof(id[0]), id, req);
SLASSERT(result);SLAndroidConfigurationItf inputConfig;
result = (*recObjectItf_)->GetInterface(recObjectItf_, SL_IID_ANDROIDCONFIGURATION, &inputConfig);
if (SL_RESULT_SUCCESS == result) {SLuint32 presetValue = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION;(*inputConfig)->SetConfiguration(inputConfig, SL_ANDROID_KEY_RECORDING_PRESET, &presetValue,sizeof(SLuint32));
}
result = (*recObjectItf_)->Realize(recObjectItf_, SL_BOOLEAN_FALSE);
SLASSERT(result);
result = (*recObjectItf_)->GetInterface(recObjectItf_, SL_IID_RECORD, &recItf_);
SLASSERT(result);
result = (*recObjectItf_)->GetInterface(recObjectItf_, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_BUFFERQUEUE, &recBufQueueItf_);
SLASSERT(result);
// 为Queue设置callback
result = (*recBufQueueItf_)->RegisterCallback(recBufQueueItf_, bqRecorderCallback, this);
SLASSERT(result);
  1. 创建一个AudioDelay类,用户处理录制好的数据。在收到录制好的数据后使用这个类根据设置的参数进行处理数据。
// 录制SLAndroidSimpleBufferQueueItf的callback中
void AudioRecorder::ProcessSLCallback(SLAndroidSimpleBufferQueueItf bq) {assert(bq == recBufQueueItf_);sample_buf *dataBuf = nullptr;// 获取到录制好的bufferdevShadowQueue_->front(&dataBuf);devShadowQueue_->pop();dataBuf->size_ = dataBuf->cap_;// callback中会处理录制的pcm数据,delay+decaycallback_(ctx_, ENGINE_SERVICE_MSG_RECORDED_AUDIO_AVAILABLE, dataBuf);recQueue_->push(dataBuf);sample_buf *freeBuf;while (freeQueue_->front(&freeBuf) && devShadowQueue_->push(freeBuf)) {freeQueue_->pop();// 给bd提供空buffer,让它填充录制的数据SLresult result = (*bq)->Enqueue(bq, freeBuf->buf_, freeBuf->cap_);SLASSERT(result);}++audioBufCount;if (devShadowQueue_->size() == 0) {(*recItf_)->SetRecordState(recItf_, SL_RECORDSTATE_STOPPED);}
}
  1. 开始录制/播放, 向SLDataLocator_AndroidSimpleBufferQueue作为AudioPlayer的输入Enqueue一个buffer,并使用SetPlayState将AudioPlayer的状态设置问题PLAYING,将AudioRecorder的状态设置RECORDING,并向它对应的SLDataLocator_AndroidSimpleBufferQueue
    塞入空buffer.
SLresult AudioPlayer::Start() {SLuint32 state;SLresult result = (*playItf_)->GetPlayState(playItf_, &state);if (result != SL_RESULT_SUCCESS) {return SL_BOOLEAN_FALSE;}if (state == SL_PLAYSTATE_PLAYING) {return SL_BOOLEAN_TRUE;}result = (*playItf_)->SetPlayState(playItf_, SL_PLAYSTATE_STOPPED);SLASSERT(result);// 插入bufferresult = (*playBufferQueueItf_)->Enqueue(playBufferQueueItf_, silentBuf_.buf_,silentBuf_.size_);SLASSERT(result);devShadowQueue_->push(&silentBuf_);result = (*playItf_)->SetPlayState(playItf_, SL_PLAYSTATE_PLAYING);SLASSERT(result);return SL_BOOLEAN_TRUE;
}
SLboolean AudioRecorder::Start() {if (!freeQueue_ || !recQueue_ || !devShadowQueue_) {return SL_BOOLEAN_FALSE;}audioBufCount = 0;SLresult result;result = (*recItf_)->SetRecordState(recItf_, SL_RECORDSTATE_STOPPED);SLASSERT(result);result = (*recBufQueueItf_)->Clear(recBufQueueItf_);SLASSERT(result);for (int i = 0; i < RECORD_DEVICE_KICKSTART_BUF_COUNT; i++) {sample_buf *buf = nullptr;if (!freeQueue_->front(&buf)) {LOGE("====OutOfFreeBuffers @ startingRecording @ (%d)", i);break;}freeQueue_->pop();assert(buf->buf_ && buf->cap_ && !buf->size_);result = (*recBufQueueItf_)->Enqueue(recBufQueueItf_, buf->buf_, buf->cap_);SLASSERT(result);devShadowQueue_->push(buf);}result = (*recItf_)->SetRecordState(recItf_, SL_RECORDSTATE_RECORDING);SLASSERT(result);return (result == SL_RESULT_SUCCESS ? SL_BOOLEAN_TRUE : SL_BOOLEAN_FALSE);
}
  1. 停止录制/播放,将AudioPlayer的状态改为STOPPED,将AudioRecorder的状态改为STOPPED,并清理队列
  2. 销毁,释放queue,调用(*engine.slEngineObj_)->Destroy(engine.slEngineObj_);销毁

原理

  1. 如何实现的decay?
    decay是根据传入的decay参数(decay 介于0.0~1.0之间)并有一段buffer,将buffer之前的数据乘以decay并加上当前的数据乘以(1-decay).
    比如decay为0.1, buffer是100
    PCM的数为:
    [1000,1000] …[2000,2000],
    pos= i pos = i+100
    最终的计算结果为[1900,1900]; 1000 * 0.1 + 2000 * 0.9. 计算好的结果又会写入到buffer中

代码:

int32_t sampleCount = channelCount_ * numFrames;
int16_t *samples = &static_cast<int16_t *>(buffer_)[curPos_ * channelCount_];
for (size_t idx = 0; idx < sampleCount; idx++) {// feedBackFactor 相当于decay,liveAudioFactor相当于1-decay// sample是之前的数据,liveAudio是当前的数据int32_t curSample = (samples[idx] * feedbackFactor_ + liveAudio[idx] * liveAudioFactor_) /kFloatToIntMapFactor;if (curSample > SHRT_MAX) {curSample = SHRT_MAX;} else if (curSample < SHRT_MIN) {curSample = SHRT_MIN;}liveAudio[idx] = samples[idx];samples[idx] = static_cast<int16_t >(curSample);
}
  1. 如何实现的delay?
    因为在为AudioRecorder创建SLDataSink以及为AudioPlayer创建SLDataSource的时候设置了format.
    根据应用输入的delay以及我们传入的采样率sampleRate可以计算出我们delay多少帧,根据我们设置的pcm格式可以算出来这些帧需要多少内存buffer.
    将内存的大小设置为它。就可以实现delay
    代码:
// delayTime 决定分配缓存的大小。
void AudioDelay::allocateBuffer() {float floatDelayTime = (float) delayTime_ / kMsPerSec;// num of frame in per second.float fNumFrames = floatDelayTime * (sampleRate_ / kMsPerSec);size_t sampleCount = static_cast<uint32_t >(fNumFrames + 0.5f) * channelCount_;uint32_t bytePerSample = format_ / 8;assert(bytePerSample <= 4 && bytePerSample);uint32_t bytePerFrame = channelCount_ * bytePerSample;bufCapacity_ = sampleCount * bytePerSample;bufCapacity_ = ((bufCapacity_ + bytePerFrame - 1) / bytePerFrame) * bytePerFrame;buffer_ = new uint8_t[bufCapacity_];assert(buffer_);memset(buffer_, 0, bufCapacity_);curPos_ = 0;bufSize_ = bufCapacity_ / bytePerSample;
}

总结

  1. OpenSL中的API操作对象的一般为:首先构造出一个object对象,再Realize一下。然后使用这个对象的GetInterface并传入一个id
    获取到一个xxxItf对象,通过这个Itf作些操作。
    如:SLEngine,首先使用slCreateEngine获取了slEngineObj,然后需要调用 (*engine.slEngineObj_)->Realize(engine.slEngineObj_, SL_BOOLEAN_FALSE)
    进行初始化,在获取slEngineItf,(*engine.slEngineObj_)->GetInterface(engine.slEngineObj_, SL_IID_ENGINE, &engine.slEngineItf_);
    AudioPlayer,AudioRecorder也使用了同样的方式。
  2. opensSL 中用的采样率为milliherz,1hz = 1000millihertz
  3. openSL函数的更过资料可以参考:https://www.khronos.org/registry/OpenSL-ES/specs/OpenSL_ES_Specification_1.1.pdf

http://chatgpt.dhexx.cn/article/1FWn8eao.shtml

相关文章

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…

Ethercat学习-从站源码移植

文章目录 简介移植源码1.源码结构2.GD32硬件接口准备1.SPI接口2.PDI中断配置3.Sync0中断配置4.Sync1中断配置5.定时器中断配置 3.移植准备4.源码移植1.修改头文件名2.ecatport.c文件修改1.SPI部分修改2.中断部分3.修改HW_Init()4.报错修改 3.myapp.c文件修改 5.其他 简介 移植…

小说php 站点源码下载,PTCMS小说站源码

必装环境&#xff1a;nginx(apache.iis 也可)&#xff0c;mysql,php5.6,memcached php5.6 安装扩展 memcache 新建站点&#xff0c;注意新建时&#xff0c;PHP 版本必须选择 PHP5.6&#xff0c;不然程序会报错 1.上传网站文件到网站目录&#xff0c;新建网站伪静态选择 thinkph…

PHP是什么

PHP 是服务器端脚本语言。 您应当具备的基础知识 在继续学习之前&#xff0c;您需要对以下知识有基本的了解&#xff1a; HTMLCSS 如果您希望首先学习这些项目&#xff0c;请在我们的 首页 访问这些教程。 PHP 是什么&#xff1f; PHP 代表 PHP: Hypertext PreprocessorPHP 是一…