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

相关文章

PostgreSQL的扩展dict_int应用案例解析

《PostgreSQL的扩展dict_int应用案例解析》dict_int扩展为PostgreSQL提供了专业的整数文本处理能力,特别适合需要精确处理数字内容的搜索场景,本文给大家介绍PostgreS... 目录PostgreSQL的扩展dict_int一、扩展概述二、核心功能三、安装与启用四、字典配置方法

深度解析Java DTO(最新推荐)

《深度解析JavaDTO(最新推荐)》DTO(DataTransferObject)是一种用于在不同层(如Controller层、Service层)之间传输数据的对象设计模式,其核心目的是封装数据,... 目录一、什么是DTO?DTO的核心特点:二、为什么需要DTO?(对比Entity)三、实际应用场景解析

深度解析Java项目中包和包之间的联系

《深度解析Java项目中包和包之间的联系》文章浏览阅读850次,点赞13次,收藏8次。本文详细介绍了Java分层架构中的几个关键包:DTO、Controller、Service和Mapper。_jav... 目录前言一、各大包1.DTO1.1、DTO的核心用途1.2. DTO与实体类(Entity)的区别1

Java中的雪花算法Snowflake解析与实践技巧

《Java中的雪花算法Snowflake解析与实践技巧》本文解析了雪花算法的原理、Java实现及生产实践,涵盖ID结构、位运算技巧、时钟回拨处理、WorkerId分配等关键点,并探讨了百度UidGen... 目录一、雪花算法核心原理1.1 算法起源1.2 ID结构详解1.3 核心特性二、Java实现解析2.

使用Python绘制3D堆叠条形图全解析

《使用Python绘制3D堆叠条形图全解析》在数据可视化的工具箱里,3D图表总能带来眼前一亮的效果,本文就来和大家聊聊如何使用Python实现绘制3D堆叠条形图,感兴趣的小伙伴可以了解下... 目录为什么选择 3D 堆叠条形图代码实现:从数据到 3D 世界的搭建核心代码逐行解析细节优化应用场景:3D 堆叠图

深度解析Python装饰器常见用法与进阶技巧

《深度解析Python装饰器常见用法与进阶技巧》Python装饰器(Decorator)是提升代码可读性与复用性的强大工具,本文将深入解析Python装饰器的原理,常见用法,进阶技巧与最佳实践,希望可... 目录装饰器的基本原理函数装饰器的常见用法带参数的装饰器类装饰器与方法装饰器装饰器的嵌套与组合进阶技巧

解析C++11 static_assert及与Boost库的关联从入门到精通

《解析C++11static_assert及与Boost库的关联从入门到精通》static_assert是C++中强大的编译时验证工具,它能够在编译阶段拦截不符合预期的类型或值,增强代码的健壮性,通... 目录一、背景知识:传统断言方法的局限性1.1 assert宏1.2 #error指令1.3 第三方解决

全面解析MySQL索引长度限制问题与解决方案

《全面解析MySQL索引长度限制问题与解决方案》MySQL对索引长度设限是为了保持高效的数据检索性能,这个限制不是MySQL的缺陷,而是数据库设计中的权衡结果,下面我们就来看看如何解决这一问题吧... 目录引言:为什么会有索引键长度问题?一、问题根源深度解析mysql索引长度限制原理实际场景示例二、五大解决

深度解析Spring Boot拦截器Interceptor与过滤器Filter的区别与实战指南

《深度解析SpringBoot拦截器Interceptor与过滤器Filter的区别与实战指南》本文深度解析SpringBoot中拦截器与过滤器的区别,涵盖执行顺序、依赖关系、异常处理等核心差异,并... 目录Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现

深度解析Spring AOP @Aspect 原理、实战与最佳实践教程

《深度解析SpringAOP@Aspect原理、实战与最佳实践教程》文章系统讲解了SpringAOP核心概念、实现方式及原理,涵盖横切关注点分离、代理机制(JDK/CGLIB)、切入点类型、性能... 目录1. @ASPect 核心概念1.1 AOP 编程范式1.2 @Aspect 关键特性2. 完整代码实