Android音频开发之OpenSL ES

2024-02-17 07:32
文章标签 音频 android es 开发 opensl

本文主要是介绍Android音频开发之OpenSL ES,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

https://www.jianshu.com/p/2b8d2de9a47b

开发Android上的音频应用,最常见的是使用MediaRecorderMediaPlayer来实现音频的录制和播放,更基础点的会使用AudioRecordAudioTrack来实现。用这两种方式已经能应对绝大部分的音频开发需求了。更底层的API,如NDK层的OpenSL ES则鲜有问津。

最近因为工作需要,接触了NDK层相关API,这里简要记录下OpenSL ES相关的知识。

关于OpenSL ES

  • OpenSL ES 官网
  • OpenSL ES Wiki
  • Google 官方的OpenSL ES介绍
  • Android OpenSL ES编程要点
  • 高性能音频基础

HelloWorld

不同于传统的HelloWorld程序,这个示例稍微复杂一点,而且这回我们的实现,将让我们听到这句经典的编程入门欢迎语。

实现思路

此处应有图,一图顶万言

  1. 创建并初始化Audio Engine(音频引擎,是和底层交互的入口)
  2. 打开OutputMix(音频输出),配置相关参数、Buffer Queue(缓冲队列),以便进行音频播放
  3. 打开Audio Input(音频输入),配置相关参数,配置Buffer Queue,以便获取音频输入
  4. 设置输出、输入的Callback(回调函数),实现将输入传给输出的逻辑
  5. 启动音频录制

也就是,这个程序实现的功能是:将话筒录制的声音,再播放出来,也就是返听的效果。

代码实现

1. 创建并初始化Audio Engine

// 创建Audio Engine
result = slCreateEngine(&openSLEngine, 0, NULL, 0, NULL, NULL);// 初始化上一步得到的openSLEngine
result = (*openSLEngine)->Realize(openSLEngine, SL_BOOLEAN_FALSE);// 获取SLEngine接口对象,后续的操作将使用这个对象
SLEngineItf openSLEngineInterface = NULL;
result = (*openSLEngine)->GetInterface(openSLEngine, SL_IID_ENGINE, &openSLEngineInterface);

2. 音频输出

2.1 打开音频输出设备

// 相关参数
const SLInterfaceID ids[] = {SL_IID_VOLUME};
const SLboolean req[] = {SL_BOOLEAN_FALSE};// 使用第一步的openSLEngineInterface,创建音频输出Output Mix
result = (*openSLEngineInterface)->CreateOutputMix(openSLEngineInterface, &outputMix, 0, ids, req);// 初始化outputMix
result = (*outputMix)->Realize(outputMix, SL_BOOLEAN_FALSE);// 由于不需要操作到ouputMix,所以这一步就不去获取它的接口对象

2.2 配置相关参数

// Buffer Queue的参数
SLDataLocator_AndroidSimpleBufferQueue outputLocator = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 };// 设置音频格式
SLDataFormat_PCM outputFormat = { SL_DATAFORMAT_PCM, 2, samplerate, SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN };// 输出源
SLDataSource outputSource = { &outputLocator, &outputFormat };// 输出管道
SLDataLocator_OutputMix outputMixLocator = { SL_DATALOCATOR_OUTPUTMIX, outputMix };
SLDataSink outputSink = { &outputMixLocator, NULL };

2.3 创建播放器

// 参数
const SLInterfaceID outputInterfaces[1] = { SL_IID_BUFFERQUEUE };
const SLboolean requireds[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE };// 创建音频播放对象AudioPlayer
result = (*openSLEngineInterface)->CreateAudioPlayer(openSLEngineInterface, &audioPlayerObject, &outputSource, &outputSink, 1, outputInterfaces, requireds);// 初始化AudioPlayer
result = (*audioPlayerObject)->Realize(audioPlayerObject, SL_BOOLEAN_FALSE);// 获取音频输出的BufferQueue接口
SLAndroidSimpleBufferQueueItf outputBufferQueueInterface = NULL;
result = (*audioPlayerObject)->GetInterface(audioPlayerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &outputBufferQueueInterface); // 获取播放器接口
SLPlayItf outputPlayInterface;
result = (*audioPlayerObject)->GetInterface(audioPlayerObject, SL_IID_PLAY, &audioPlayerInterface);

3. 音频输入

3.1 配置参数

// 参数
SLDataLocator_IODevice deviceInputLocator = { SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, NULL };
SLDataSource inputSource = { &deviceInputLocator, NULL };SLDataLocator_AndroidSimpleBufferQueue inputLocator = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 };
SLDataFormat_PCM inputFormat = { SL_DATAFORMAT_PCM, 2, samplerate * 1000, SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN };SLDataSink inputSink = { &inputLocator, &inputFormat };const SLInterfaceID inputInterfaces[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION };const SLboolean requireds[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE };

3.2 创建录制器

// 创建AudioRecorder
result = (*openSLEngineInterface)->CreateAudioRecorder(openSLEngineInterface, &andioRecorderObject, &inputSource, &inputSink, 2, inputInterfaces, requireds);// 初始化AudioRecorder
result = (*andioRecorderObject)->Realize(andioRecorderObject, SL_BOOLEAN_FALSE);// 获取音频输入的BufferQueue接口
result = (*andioRecorderObject)->GetInterface(andioRecorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &inputBufferQueueInterface);// 获取录制器接口
SLRecordItf audioRecorderInterface;
(*andioRecorderObject)->GetInterface(andioRecorderObject, SL_IID_RECORD, &audioRecorderInterface);

4. 配置回调并启动输入、输出

4.1 配置输入、输出回调

// 输出回调
result = *outputBufferQueueInterface)->RegisterCallback(outputBufferQueueInterface, outputCallback, NULL);// 输入回调
result = (*inputBufferQueueInterface)->RegisterCallback(inputBufferQueueInterface, inputCallback, NULL);

4.2 启动输入输出

// 设置为播放状态
result = (*audioPlayerInterface)->SetPlayState(audioPlayerInterface, SL_PLAYSTATE_PLAYING);
// 设为录制状态
result = (*andioRecorderObject)->SetRecordState(andioRecorderObject, SL_RECORDSTATE_RECORDING);// 启动回调机制
(*inputBufferQueueInterface)->Enqueue(inputBufferQueueInterface, inputBuffers[0], buffersize * 4);
(*outputBufferQueueInterface)->Enqueue(outputBufferQueueInterface, outputBuffers[0], buffersize * 4);

4.3 回调函数的实现

// 音频输入回调
static void inputCallback(SLAndroidSimpleBufferQueueItf bufferQueue, void *pContext) {// 获取同步锁pthread_mutex_lock(&mutex);// 取一个可用的缓存short int *inputBuffer = inputBuffers[inputBufferWrite];if (inputBuffersAvailable == 0) inputBufferRead = inputBufferWrite;// 可用缓存+1inputBuffersAvailable++;if (inputBufferWrite < numBuffers - 1) inputBufferWrite++; elseinputBufferWrite = 0;pthread_mutex_unlock(&mutex);// 调用BufferQueue的Enqueue方法,把输入数据取到inputBuffer(*bufferQueue)->Enqueue(bufferQueue, inputBuffer, buffersize * 4);
}// 音频输出回调
static void outputCallback(SLAndroidSimpleBufferQueueItf bufferQueue, void *pContext) {short int *outputBuffer = outputBuffers[outputBufferIndex];pthread_mutex_lock(&mutex);if (inputBuffersAvailable < 1) {pthread_mutex_unlock(&mutex);memset(outputBuffer, 0, buffersize * 4);} else {short int *inputBuffer = inputBuffers[inputBufferRead];if (inputBufferRead < numBuffers - 1) inputBufferRead++; else inputBufferRead = 0;inputBuffersAvailable--;pthread_mutex_unlock(&mutex);memcpy(outputBuffer, inputBuffer, buffersize * 4);}(*bufferQueue)->Enqueue(bufferQueue, outputBuffer, buffersize * 4);if (outputBufferIndex < numBuffers - 1) outputBufferIndex++; else outputBufferIndex = 0;
}

回调函数使用生产者-消费者机制实现,当输入可用的时候,就从输入的缓冲队列里取数据出来,放到inputBuffers里;然后当输出准备就绪的时候,再从inputBuffers里取出数据,复制一份,然后放入输出的缓冲队列里。就这样实现了把音频输出转到音频输出的效果。

关于OpenSL的使用

使用OpenSL相关API的通用步骤是:

  1. 创建对象(通过带有create的函数)
  2. 初始化(通过Realize函数)
  3. 获取接口来使用相关功能(通过GetInterface函数)

OpenSL使用回调机制来访问音频IO,但不像跟Jack、CoreAudio那些音频异步IO框架,OpenSL 的回调里并不会把音频数据作为参数传递,回调方法仅仅是告诉我们:BufferQueue已经就绪,可以接受/获取数据了。

使用SLBufferQueueItf. Enqueue函数从(往)音频设备获取(放入)数据。完整的函数签名是:SLresult (*Enqueue) (SLBufferQueueItf self, const void *pBuffer, SLuint32 size);

当BufferQueue就绪,这个方法就应被调用。当开启录制或开始播放时,BufferQueue就可以接受数据。这之后,回调机制通过回调来告知应用程序它已经准备好,可以消费(提供)数据。

Enqueue方法可以在回调里调用,可以不。

如果选择在回调里调用,那么在开始播放(录制)的时候,需要先调用Enqueue来启动回调机制,否则回调将不会被调用到。

如果选择不在回调里调用,回调则用于通知程序,它准备就绪了。程序可以在得到足够的数据缓存之后,再把数据给它处理。这示例使用的是前一种方式,在回调里调用Enqueue

That's all

使用OpenSL ES可以更高效的使用Android的音频系统,尤其是需要低延迟的场景,如返听。随着Android设备性能的提升,以及Android系统的不断优化,音频延迟的问题已经有了可观的性能提升。而在游戏领域,OpenSL ES的高性能也能提供更棒的游戏体验,甚至让移动平台也有打造《吟诵者(In Verbis Virtus)》这种语音类游戏的可能。

But not ALL

对于OpenSL ES也仅仅是草草接触,如有不对或疏漏的地方,还请大家指正。



作者:罗力
链接:https://www.jianshu.com/p/2b8d2de9a47b
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

这篇关于Android音频开发之OpenSL ES的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

活用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影

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

【区块链 + 人才服务】区块链集成开发平台 | FISCO BCOS应用案例

随着区块链技术的快速发展,越来越多的企业开始将其应用于实际业务中。然而,区块链技术的专业性使得其集成开发成为一项挑战。针对此,广东中创智慧科技有限公司基于国产开源联盟链 FISCO BCOS 推出了区块链集成开发平台。该平台基于区块链技术,提供一套全面的区块链开发工具和开发环境,支持开发者快速开发和部署区块链应用。此外,该平台还可以提供一套全面的区块链开发教程和文档,帮助开发者快速上手区块链开发。

Vue3项目开发——新闻发布管理系统(六)

文章目录 八、首页设计开发1、页面设计2、登录访问拦截实现3、用户基本信息显示①封装用户基本信息获取接口②用户基本信息存储③用户基本信息调用④用户基本信息动态渲染 4、退出功能实现①注册点击事件②添加退出功能③数据清理 5、代码下载 八、首页设计开发 登录成功后,系统就进入了首页。接下来,也就进行首页的开发了。 1、页面设计 系统页面主要分为三部分,左侧为系统的菜单栏,右侧