Android Open SL ES — 官方Demo解析native-audio

2023-11-01 03:50

本文主要是介绍Android Open SL ES — 官方Demo解析native-audio,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Open SL ES简介
OpenSL ES – 嵌入式音频加速标准。OpenSL ES™ 是无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速API。它为嵌入式移动多媒体设备上的本地应用程序开发者提供标准化, 高性能,低响应时间的音频功能实现方法,并实现软/硬件音频性能的直接跨平台部署,降低执行难度,促进高级音频市场的发展。在Android中主要用到了一部分Open SL ES的功能,两者之间有交集但不完全一样,Android有自己的一部分扩展。
在这里插入图片描述
主要功能介绍
1、Assets目录音频播放
2、c头文件形式播放
3、本地音频播放
4、录音和回放
本文中主要介绍从缓冲队列中播放和录音到缓冲队列中,主要用到以下几个native方法。其他方法操作步骤类似,可以参考学习。
代码解析:

 public static native void createEngine();public static native void createBufferQueueAudioPlayer(int sampleRate, int samplesPerBuf);public static native boolean createAudioRecorder();public static native void startRecording();public static native void shutdown();

从native方法的排序可以看出程序的调用流程
在这里插入图片描述
在这里插入图片描述
录音流程:
从图中可以看出不管是播放还是录音都必须先创建引擎,在Open SL ES中所有的对象创建过程都是一样的步骤。
1、create
2、Realize
3、GetInterface
一、Engine的创建基本就是这几个步骤,Interface 可以根据自己的业务需求来获取,不需要的可以删除。

// 创建引擎result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
// 获取引擎接口result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineItf);// create output mix, with environmental reverb specified as a non-required interface// const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB};// SLboolean req[1] = {SL_BOOLEAN_FALSE};result = (*engineItf)->CreateOutputMix(engineItf, &outputMixObject, 0, NULL, NULL);assert(SL_RESULT_SUCCESS == result);(void) result;
// realize the output mixresult = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);

二、 createBufferQueueAudioPlayer
创建的步骤和引擎类似,从上面的图片中可以出,多了一些东西需要配置,DataSource 和 DataSink,DataSource 顾名思义就是要播放的数据源,缓冲队列的播放形式只支持PCM格式的数据源,DataSink 就是音频输出,也就是创建引擎时获取的 outputMixObject 对象。PCM 数据源需要配置采样率、通道、定点、扬声器、小端模式,参数配置需要参考官方文档,有些参数是不支持的,需要测试。另一个比较重要的就是缓冲队列和播放回调,因为和录音一样,所以放到下面一起介绍。

    // create BufferQueueAudioPlayer// configure audio sourceSLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, SL_BUFFERSIZE};SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM,1,SL_SAMPLINGRATE_32,SL_PCMSAMPLEFORMAT_FIXED_16,SL_PCMSAMPLEFORMAT_FIXED_16,SL_SPEAKER_FRONT_CENTER,SL_BYTEORDER_LITTLEENDIAN}; //合适pcm  单通道   采样率   定点    扬声器  小端
//    if (sampleRate) {
//        format_pcm.samplesPerSec = (SLuint32) sampleRate;
//    }SLDataSource audioSrc = {&loc_bufq, &format_pcm};SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};SLDataSink audioSnk = {&loc_outmix, NULL};//需要请求的接口   缓冲队列   音量const SLInterfaceID idsAudioPlayer[3] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};const SLboolean reqAudioPlayer[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};result = (*engineItf)->CreateAudioPlayer(engineItf, &playerObject, &audioSrc, &audioSnk, 2,idsAudioPlayer, reqAudioPlayer);assert(SL_RESULT_SUCCESS == result);(void) result;// realize the playerresult = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerItf);result = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &playerBufferQueueItf);result = (*playerBufferQueueItf)->RegisterCallback(playerBufferQueueItf, playCallback, audioProcess);//静音模式/solo不支持单声道// mute/solo is not supported for sources that are known to be mono, as this is// get the mute/solo interface//result = (*playerObject)->GetInterface(playerObject, SL_IID_MUTESOLO, &playerMuteSoloItf);// get the volume interfaceresult = (*playerObject)->GetInterface(playerObject, SL_IID_VOLUME, &playerVolumeItf);

三 、createAudioRecorder
录音的创建和播放类似,只是它们的 DataSource 和 DataSink 不同,录音的数据源 DataSource 是来自麦克风,输出数据DataSink 和 播放的数据源是一样的,PCM格式的数据,配置方式也是一样。

  //录音// configure audio sourceSLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};SLDataSource recordAudioSrc = {&loc_dev, NULL};// configure audio sinkSLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, SL_BUFFERSIZE};//缓冲的数量SLDataFormat_PCM recordFormat_pcm = {SL_DATAFORMAT_PCM,1,SL_SAMPLINGRATE_32,SL_PCMSAMPLEFORMAT_FIXED_16,SL_PCMSAMPLEFORMAT_FIXED_16,SL_SPEAKER_FRONT_CENTER,SL_BYTEORDER_LITTLEENDIAN};
//    if (sampleRate) {
//        recordFormat_pcm.samplesPerSec = (SLuint32) sampleRate;
//    }SLDataSink recordSink = {&loc_bq, &recordFormat_pcm};const SLInterfaceID idsRecord[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};const SLboolean reqRecord[1] = {SL_BOOLEAN_TRUE};//创建录音对象result = (*engineItf)->CreateAudioRecorder(engineItf,&recorderObject,&recordAudioSrc,&recordSink,1,idsRecord,reqRecord);assert(SL_RESULT_SUCCESS == result);(void) result;// realize the audio recorderresult = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE);assert(SL_RESULT_SUCCESS == result);(void) result;// 获取录音接口result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderItf);result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,&recorderBufferQueueItf);result = (*recorderBufferQueueItf)->RegisterCallback(recorderBufferQueueItf, recordCallback,audioProcess);

四、播放和录音的缓冲队列机制
在创建播放和录音的时候都设置了缓冲队列和回调,注意播放和录音代码中都配置了一个 SL_BUFFERSIZE 参数,这是我自定义的一个宏,根据名字可以推测这是设置缓冲队列的大小,也就是当前 BufferQueue 中最大可以容纳的buffer数量。 每次播完或录完一个 buffer 都会回调创建时 RegisterCallback 传入的callback方法,代表当前buffer数据已经处理完毕,buffer出队, 如果是录音你应该获取这个 buffer 保存下来,写文件或者其他操作都可以;如果是播放就代表 当前buffer已经播放完毕,你可以 Enqueue 下一个buffer了。Enqueue到队列中的buffer数量超过了你设置的 SL_BUFFERSIZE 值,会报错SL_RESULT_BUFFER_INSUFFICIENT。

五、需要注意的坑
当你需要停止录音和停止播放的时候,调用以下代码,官方案例也是这么做的,但是这么做会有bug,当你下次再开始录音或播放的时候,会报错SL_RESULT_BUFFER_INSUFFICIENT,也就是说队列满了。根据测试发现Clear函数并不起作用。因为设置了SL_PLAYSTATE_STOPPED状态,录音或播放已经停止了,所以不会再回调callback函数了,也就是代表剩余的buffer没用出队,所以会报错。

//停止播放SLresult result = (*playerItf)->SetPlayState(playerItf, SL_PLAYSTATE_STOPPED);assert(SL_RESULT_SUCCESS == result);(void) result;result = (*playerBufferQueueItf)->Clear(playerBufferQueueItf);assert(SL_RESULT_SUCCESS == result);(void) result;
 // 停止录音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;

解决办法:
思路一:
在callback中判断缓冲队列的状态,等到buffer全部出队再停止播放或录音,这样就会有延迟,延迟时间是根据你设置的 SL_BUFFERSIZE 队列大小来决定的,所以 SL_BUFFERSIZE 不宜设置太大,官方DEMO推荐至少设置2个,buffer数组也不宜过大,假设采样率是32000,buffer是 short *buffer[512] 延迟是16毫秒,2个就是32毫秒,这样的延迟几乎可以忽略了。

 AudioProcess *audioProcess = context;SLAndroidSimpleBufferQueueState recQueueState;//缓冲区状态(*bq)->GetState(bq, &recQueueState);
      if (recQueueState.count == 0) {SLresult result = (*audioProcess->playerItf)->SetPlayState(audioProcess->playerItf, SL_PLAYSTATE_STOPPED);assert(SL_RESULT_SUCCESS == result);(void) result;result = (*audioProcess->playerBufferQueueItf)->Clear(audioProcess->playerBufferQueueItf);assert(SL_RESULT_SUCCESS == result);(void) result;LOGD("停止播放");}

思路二:
下次开始录制或播放的时候重置buffer数据,接着走回调函数,直接在callback中控制流程,这样就相当于 SL_PLAYSTATE_STOPPED 是一个暂停的状态,可以避免播放和录音的启动时间,也是一种不错的方案。
参考资料:
官方demo
Open SL ES相关的博客
Open SL ES 资料:ndk目录/docs/Additional_library_docs/opensles/OpenSL_ES_Specification_1.0.1.pdf

这篇关于Android Open SL ES — 官方Demo解析native-audio的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/320275

相关文章

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

在C#中合并和解析相对路径方式

《在C#中合并和解析相对路径方式》Path类提供了几个用于操作文件路径的静态方法,其中包括Combine方法和GetFullPath方法,Combine方法将两个路径合并在一起,但不会解析包含相对元素... 目录C#合并和解析相对路径System.IO.Path类幸运的是总结C#合并和解析相对路径对于 C

Java解析JSON的六种方案

《Java解析JSON的六种方案》这篇文章介绍了6种JSON解析方案,包括Jackson、Gson、FastJSON、JsonPath、、手动解析,分别阐述了它们的功能特点、代码示例、高级功能、优缺点... 目录前言1. 使用 Jackson:业界标配功能特点代码示例高级功能优缺点2. 使用 Gson:轻量

Java如何接收并解析HL7协议数据

《Java如何接收并解析HL7协议数据》文章主要介绍了HL7协议及其在医疗行业中的应用,详细描述了如何配置环境、接收和解析数据,以及与前端进行交互的实现方法,文章还分享了使用7Edit工具进行调试的经... 目录一、前言二、正文1、环境配置2、数据接收:HL7Monitor3、数据解析:HL7Busines

python解析HTML并提取span标签中的文本

《python解析HTML并提取span标签中的文本》在网页开发和数据抓取过程中,我们经常需要从HTML页面中提取信息,尤其是span元素中的文本,span标签是一个行内元素,通常用于包装一小段文本或... 目录一、安装相关依赖二、html 页面结构三、使用 BeautifulSoup javascript

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo