音视频开发进阶指南(第四章)-OpenSL-ES播放PCM音频(下)

简介: 笔记

创建接口过程

创建接口有两种方法:

  1. CreateXXX,这种获取的都是通用类型的接口,需要实例化
  2. GetInterface,获取的是具体类型的接口,因为它需要传入接口类型ID,不需要实例化


实例化过程

实例化就是自己给自己实例化,所有类型的实例化是固定的方法:

//obj是通用类型
//第二个参数表示是否异步执行 一般为false
(*obj)->Realize(obj, SL_BOOLEAN_FALSE);

播放的初始化工作是比较麻烦的,参数非常多,关键参数一定要弄清楚,否则不知其所以然。


1.1 引擎对象

想要调用OpenSL的API,它有一个唯一的门口slCreateEngine,很多文章里叫它引擎,我就叫引擎门口,直观一点,门口里面还有其它的小门口。

SLObjectItf engineObj;  //API门口
//1.1获取引擎对象接口
SLresult result = slCreateEngine(&engineObj, 0, 0, 0, 0, 0);
//1.2 SLObjectItf 类型,需要实例化门口引擎对象接口
result = (*engineObj)->Realize(engineObj, SL_BOOLEAN_FALSE);

1.2 获取引擎管理接口

有了引擎对象,接下来就要获取需要的引擎管理接口了,OpenSL有多种引擎管理接口,通过ID区分,例如下面的SL_IID_ENGINE

SLEngineItf engineEngine;
//2.1获取SLEngineItf类型引擎接口,后续操作将会使用这个接口
result = (*engineObj)->GetInterface(engineObj, SL_IID_ENGINE, &engineEngine);
//SLEngineItf 是具体类型不需要实例化

1.3 音频混音

混音器用于将多个音频混合并且输出到喇叭

SLObjectItf outputMixObj;
const SLInterfaceID ids[] = {SL_IID_VOLUME};
const SLboolean req[] = {SL_BOOLEAN_FALSE};
 //3.1创建音频输出混音对象接口
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObj, 0, ids, req);
//3.2 SLObjectItf 类型,实例化音频输出混音对象接口
result = (*outputMixObj)->Realize(outputMixObj, SL_BOOLEAN_FALSE);
//3.3 配置输出管道
SLDataLocator_OutputMix outputMixLocator = {SL_DATALOCATOR_OUTPUTMIX, outputMixObj};
SLDataSink outputSink = {&outputMixLocator, NULL};
// 配置输出源
 //4.1配置缓冲区Buffer Queue参数
 outputLocator = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
 //4.2设置音频源的音频格式
 SLDataFormat_PCM outputFormat = {
                                  SL_DATAFORMAT_PCM,   //指定PCM格式
                                  2,                         //通道个数
                                  SL_SAMPLINGRATE_44_1,           //采样率
                                  SL_PCMSAMPLEFORMAT_FIXED_16,//采样精度
                                  SL_PCMSAMPLEFORMAT_FIXED_16,//窗口大小
                                  SL_SPEAKER_FRONT_LEFT | 
                                  SL_SPEAKER_FRONT_RIGHT,//通道掩码
                                  SL_BYTEORDER_LITTLEENDIAN  //字节序:小端
 };
 //4.3输出源
 SLDataSource outputSource = {&outputLocator, &outputFormat};

1.4 获取播放器对象门口

播放器门口不是具体执行播放的工具,而是管理播放相关的缓冲,音频格式,混音,输出等

//5.1获取播放器对象接口
SLObjectItf audioPlayerObj;
const SLInterfaceID outputInterfaces[1] = {SL_IID_BUFFERQUEUE};
const SLboolean requireds[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE};
result = (*engineEngine)->CreateAudioPlayer(engineEngine,
                                   &audioPlayerObj,
                                   &outputSource,//输出源
                                   &outputSink,//输出管道
                                   1,//接口个数
                                   outputInterfaces,//输出接口
                                   requireds);  //接口配置
//看到了没,又是SLObjectItf 类型,还得实例化
//5.2实例化播放器对象接口
result = (*audioPlayerObj)->Realize(audioPlayerObj, SL_BOOLEAN_FALSE);

1.5 音频输出对象

音频输出对象就是音频数据本身,具体一点就是存放即将被播放的数据所在的缓冲区

//6.1获取具体音频输出对象接口
SLAndroidSimpleBufferQueueItf outputBufferQueueInterface;
 result = (*audioPlayerObj)->GetInterface(audioPlayerObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
                                 &outputBufferQueueInterface);
//SLAndroidSimpleBufferQueueItf 是具体类型,不用实例化

1.6 具体的播放器对象

它是用来执行播放功能的,其它的条件都给它准备好了

SLPlayItf audioPlayerPlay;
//7.1获取播放器播放对象接口
result = (*audioPlayerObj)->GetInterface(audioPlayerObj, SL_IID_PLAY, &audioPlayerPlay);
if (result != SL_RESULT_SUCCESS) {
    LOGD("audioPlayerObj SL_IID_PLAY GetInterface failed,result=%d", result);
    return result;
}
//具体类型,不用实例化

1.7 设置回调

回调函数的作用是:通知。

通知什么?在播放的时候,OpenSL不会一次性把所有数据都读到缓冲区,需要用一点,拷贝一点,这个函数就是播放器告诉你,缓存用光了,需要新的数据。

所以在回调函数中需要把新的数据拷贝到缓冲区。

//8.1设置回调
result = (*outputBufferQueueInterface)->RegisterCallback(outputBufferQueueInterface,
                                                PlayCallback,
                                                this);

二、开始播放


//9设置为播放状态
(*audioPlayerPlay)->SetPlayState(audioPlayerPlay, SL_PLAYSTATE_PLAYING);
LOGI("setPlayerState:SL_PLAYSTATE_PLAYING");
//10启动回调机制,开始播放
PlayCallback(outputBufferQueueInterface, this);


三、写数据


前面说了,回调函数中需要填充新的数据:

SLuint32 getPcmData(void **pcm, FILE *pcmFile, uint8_t *out_buffer) {
    while (!feof(pcmFile)) {
        //因为PCM采样率为44100,采样精度为16BIT,所以一次读取2秒钟的采样
        size_t size = fread(out_buffer, 1, 44100 * 2 * 2, pcmFile);
        *pcm = out_buffer;
        return size;
    }
    return 0;
}
//当outputBufferQueueInterface中的数据消耗完就会触发回调
void PlayCallback(SLAndroidSimpleBufferQueueItf bufferQueue, void *pContext) {
    LOGI("PlayCallback");
    //获取数据
    SLuint32 size = getPcmData(&readPCMBuffer, pcmFile, tempBuffer);
    LOGI("PlayCallback, size=%d", size);
    if (NULL != readPCMBuffer && size > 0) {
        SLresult result = (*outputBufferQueueInterface)->Enqueue(outputBufferQueueInterface,
                                                                 readPCMBuffer, size);
    }
}

停止播放


//11.停止播放
(*audioPlayerPlay)->SetPlayState(audioPlayerPlay, SL_PLAYSTATE_STOPPED);

释放OpenSL ES资源


只需要销毁OpenSL ES对象,接口不需要做Destroy处理

(*engineObj)->Destroy(engineObj);
(*outputMixObj)->Destroy(outputMixObj);
(*audioPlayerObj)->Destroy(audioPlayerObj);
目录
相关文章
|
移动开发 前端开发 API
鸿蒙web加载本地网页资源异常
在鸿蒙NEXT Api 12中,为解决Web组件加载本地资源(如图片、CSS等)失败的问题,我们采用拦截机制。具体步骤如下: 1. **替换路径**:通过正则表达式将HTML和CSS中的资源路径替换为带有标记的URL(如`http://local`),以便后续识别。 2. **拦截与返回**:在资源加载时,拦截带有标记的URL,读取对应的本地文件并返回给Web组件。此过程确保了本地资源能正确加载和显示。 代码实现包括路径替换、资源拦截及响应构建,确保Web页面能够顺利加载本地资源。
688 7
|
消息中间件 存储 缓存
不看损失大了,刨根问底,Kafka消息中间件到底会不会丢消息
不看损失大了,刨根问底,Kafka消息中间件到底会不会丢消息
1445 98
不看损失大了,刨根问底,Kafka消息中间件到底会不会丢消息
|
Java Unix Linux
开源项目推荐:IM开源即时通讯软件收集,请重点关注Telegram/野火/flamingo
开源项目推荐:IM开源即时通讯软件收集,请重点关注Telegram/野火/flamingo
5177 0
|
Java 测试技术 持续交付
Java一分钟之-Spring Cloud Contract:契约测试
【6月更文挑战第16天】Spring Cloud Contract是微服务契约测试框架,通过DSL定义接口行为,使用WireMock生成存根进行独立开发验证。常见问题包括契约编写不清晰、未集成到CI/CD和契约版本控制混乱。例如,定义一个`GET /greeting`返回JSON响应的契约,Spring Cloud Contract会自动生成测试代码,帮助确保服务间接口一致性,提升开发效率和系统稳定性。
583 7
|
运维 Linux Shell
运维:Linux服务器崩了怎么办,快来看看这份”急救命令指南“吧!
当服务器出现问题,如崩溃、内存耗尽或CPU使用率过高时,运维工程师需要保持冷静,并通过一系列Shell命令来诊断和解决。首先,检查是否有异常SSH登录活动,查看`/etc/passwd`和`.bash_history`文件,以及用户最近的登录信息。接着,监控网络连接和端口,使用`netstat`和`lsof`命令找出资源占用高的进程,并查看进程启动时间和详细信息。同时,排查可能的恶意文件,检查定时任务和服务配置以确保没有异常启动项。最后,分析系统日志,如`/var/log`目录下的各种日志文件,找出潜在问题。通过这些步骤,可以有效定位和解决服务器故障。
|
存储 缓存 Unix
Cmake 链接外部库:如何在项目中使用外部库和头文件
Cmake 链接外部库:如何在项目中使用外部库和头文件
1751 0
|
人工智能 API 语音技术
HarmonyOS学习路之开发篇—AI功能开发(语音播报)
语音播报(Text to Speech,下文简称TTS),基于华为智慧引擎(HUAWEI HiAI Engine)中的语音播报引擎,向开发者提供人工智能应用层API。该技术提供将文本转换为语音并进行播报的能力。
|
JavaScript 前端开发 Shell
Github 部署个人网页 | 一键部署
相信不少人最听说过 Github 部署网站,但是我翻找了很多文章基本以实操为主,在 Setting 点一下就没了。 虽然 Github 部署已经很简单了,但是里面还是有不少细节的,这篇文章除了手把手教大家部署一个网站,还会聊一些关于部署的细节。
Github 部署个人网页 | 一键部署
|
网络协议 开发者
scrcpy投屏工具的在harmonyOS开发上的使用
文章目录 前言 1.手机配置 2.安装与下载 3.添加环境变量 4.USB连接使用方法 5.wifi连接方法 6.快捷设置 6.鸿蒙开发用途
scrcpy投屏工具的在harmonyOS开发上的使用
|
缓存 小程序 数据库
微信小游戏开发最全系列教程
嗨!大家好,我是小蚂蚁。 最近有非常多的新朋友关注了我,再加上这一段时间里,我又写了很多的原创教程,所以,更新一下这篇“微信小游戏开发最全系列教程”,做了很多的补充,希望能够对你有帮助,可以把这篇文章收藏一下,以便在需要的时候可以找得到,因为很可能这是全网最好的微信小游戏开发系列教程资料了。 这里的教程文章已经帮助过很多人做出了自己的第一款游戏,希望它们还能够继续帮助更多想要做游戏的朋友,进入游戏开发世界的大门。
849 0

热门文章

最新文章

下一篇
开通oss服务