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

相关文章

网页解析 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

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

native和static native区别

本文基于Hello JNI  如有疑惑,请看之前几篇文章。 native 与 static native java中 public native String helloJni();public native static String helloJniStatic();1212 JNI中 JNIEXPORT jstring JNICALL Java_com_test_g

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

OWASP十大安全漏洞解析

OWASP(开放式Web应用程序安全项目)发布的“十大安全漏洞”列表是Web应用程序安全领域的权威指南,它总结了Web应用程序中最常见、最危险的安全隐患。以下是对OWASP十大安全漏洞的详细解析: 1. 注入漏洞(Injection) 描述:攻击者通过在应用程序的输入数据中插入恶意代码,从而控制应用程序的行为。常见的注入类型包括SQL注入、OS命令注入、LDAP注入等。 影响:可能导致数据泄

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。