34.FFmpeg+OpenGLES+OpenSLES播放器实现(八.OpenSLES播放音频)

简介: 项目源码OpenSL ES 文档 OpenSLES:(Open Sound Library for Embedded Systems) OpenSLES是跨平台、针对嵌入式系统精心优化的硬件音频加速API。

项目源码
OpenSL ES 文档

OpenSLES:(Open Sound Library for Embedded Systems)

OpenSLES是跨平台、针对嵌入式系统精心优化的硬件音频加速API。使用OpenSLES进行音频播放的好处是可以不依赖第三方。它为嵌入式移动多媒体设备上的本地应用程序开发者提供标准化, 高性能,低响应时间的音频功能实现方法,并实现软/硬件音频性能的直接跨平台部署,降低执行难度。

今天我们就通过OpenSLES代码来实现播放pcm格式的音频文件,后期会将相关代码整合到ffmpeg播放器上,实现音视频同步。下边是OpenSLES播放音频的简要流程(图片来自网上)

img_06c112bc67c418437b9da09ac6a80b68.png
OpenSLES音频播放流程.png

接下来进入代码阶段

首先使用OpenSLES需要将这个库链接到我们的so中,在CMakeLists.txt脚本中添加

target_link_libraries( 
                       ......
                       #播放音频
                       OpenSLES )
实现流程

1.创建引擎
slCreateEngine(),第一个参数是要创建的引擎对象,是一个SLObjectItf类型,Object是一个资源的抽象集合,可以通过它获取各种资源,所有的Object在OpenSL里面我们拿到的都是一个SLObjectItf,返回值是SLresult类型,如果成功则返回SL_RESULT_SUCCESS,其他参数都传0即可,我们务求最精简的实现功能

SLObjectItf             *pEngine,
SLuint32                numOptions,
const SLEngineOption    *pEngineOptions,
SLuint32                numInterfaces,
const SLInterfaceID     *pInterfaceIds,
const SLboolean         * pInterfaceRequired

2.引擎对象创建后实例化,创建出来之后必须先调用Realize方法做初始化。在不需要使用的时候调用Destroy方法释放资源
(*slObjectItf)->Realize(),实例化成功则返回SL_RESULT_SUCCESS

SLObjectItf self, //要被实例化的引擎对象本身
SLboolean async //传SL_BOOLEAN_FALSE

3.引擎实例化之后从引擎对象获取接口
Interface则是方法的集合,例如SLRecordItf里面包含了和录音相关的方法,SLPlayItf包含了和播放相关的方法。我们功能都是通过调用Interfaces的方法去实现的,个Object里面可能包含了多个Interface,所以GetInterface方法有个SLInterfaceID参数来指定到的需要获取Object里面的那个Interface。调用这个方法会获取到SLEngineItf,SLEngineItf是OpenSL里面最重要的一个Interface,我们可以通过它去创建各种Object,例如播放器、录音器、混音器的Object,然后在用这些Object去获取各种Interface去实现各种功能

(*slObjectItf)->GetInterface() 执行会后会获取到SLEngineItf slEngineItf对象

SLObjectItf self,  //实例化后的引擎对象
const SLInterfaceID iid, //SL_IID_ENGINE
void * pInterface //输出的接口对象指针

4.创建混音器
混音器是从创建出的引擎接口中创建的
(*slEngineItf)->CreateOutputMix(),输出的混音器同样是SLObjectItf mixObjectItf类型

SLEngineItf self,  //引擎接口
SLObjectItf * pMix, //输出的混音器
SLuint32 numInterfaces,//传0
const SLInterfaceID * pInterfaceIds,//传0
const SLboolean * pInterfaceRequired //传0

5.混音器对象创建后开始实例化,同引擎实例化
(*mixObjectItf)->Realize()

SLObjectItf self, //要被实例化的混音器对象本身
SLboolean async //传SL_BOOLEAN_FALSE

6.创建播放器
(*slEngineItf)->CreateAudioPlayer(),播放器是从引擎对象创建出来的

SLEngineItf self, //引擎对象本身
SLObjectItf * pPlayer, //输出的播放器对象,同样是SLObjectItf类型
SLDataSource *pAudioSrc,//数据的来源
SLDataSink *pAudioSnk, //数据的去处,和SLDataSource是相对的
SLuint32 numInterfaces,//与下面的SLInterfaceID和SLboolean配合使用,用于标记SLInterfaceID数组和SLboolean的大小
const SLInterfaceID * pInterfaceIds, //这里需要传入一个数组,指定创建的播放器会包含哪些Interface
const SLboolean * pInterfaceRequired  //这里也是一个数组,用来标记每个需要包含的Interface

7.初始化播放器
(*slPlayerItf)->Realize() 得到SLObjectItf slPlayerItf = NULL;

SLObjectItf self, //要被实例化的播放器对象本身
SLboolean async //传SL_BOOLEAN_FALSE

8.获取播放器接口
(*slPlayerItf)->GetInterface(slPlayerItf, SL_IID_PLAY, &slPlayItf);
得到播放器接口SLPlayItf slPlayItf

SLObjectItf self,  //实例化后的播放器对象
const SLInterfaceID iid, //SL_IID_ENGINE
void * pInterface //输出的接口对象指针

9,获取播放队列接口
(*slPlayerItf)->GetInterface(slPlayerItf, SL_IID_BUFFERQUEUE, &pcmQueue)

SLObjectItf self, 
const SLInterfaceID iid, //SL_IID_ENGINE
void * pInterface //输出的接口对象指针

10.给播放队列设置回调函数
(*pcmQueue)->RegisterCallback(pcmQueue, CallBack, 0);
开始播放后会不断的回调这个函数将音频数据压入队列

void CallBack(SLAndroidSimpleBufferQueueItf bf, void *contex) {
    LOGI("CallBack ....");
    static FILE *fp = NULL;
    static char *buf = NULL;
    if (!buf) {
        buf = new char[1024 * 1024];
    }
    if (!fp) {
        fp = fopen("/sdcard/test.pcm", "rb");
    }
    if (!fp)return;
    if (feof(fp) == 0) {
        int len = fread(buf, 1, 1024, fp);
        if (len > 0)
            (*bf)->Enqueue(bf, buf, len);
    }
}

11.设置播放状态为播放中

(*slPlayItf)->SetPlayState(slPlayItf, SL_PLAYSTATE_PLAYING);

12.开始播放

(*pcmQueue)->Enqueue(pcmQueue, "", 1);
img_9359188d72721a3b0309b5964ef98e19.png
播放流程.png
完整代码
void CallBack(SLAndroidSimpleBufferQueueItf bf, void *contex) {
    LOGI("CallBack ....");
    static FILE *fp = NULL;
    static char *buf = NULL;
    if (!buf) {
        buf = new char[1024 * 1024];
    }
    if (!fp) {
        fp = fopen("/sdcard/test.pcm", "rb");
    }
    if (!fp)return;
    if (feof(fp) == 0) {
        int len = fread(buf, 1, 1024, fp);
        if (len > 0)
            (*bf)->Enqueue(bf, buf, len);
    }
}

/**
 * 播放音频
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_rzm_ffmpegplayer_FFmpegPlayer_playAudio(JNIEnv *env, jobject instance, jstring url_) {
    const char *url = env->GetStringUTFChars(url_, 0);

    /***********  1 创建引擎 获取SLEngineItf***************/
    SLObjectItf slObjectItf;
    SLEngineItf slEngineItf;
    //SLresult是unsigned int 类型
    SLresult result;
    result = slCreateEngine(&slObjectItf, 0, 0, 0, 0, 0);
    if (result != SL_RESULT_SUCCESS)
        return;
    //SLObjectItf本身是一个指针,*slObjectItf得到的是他的对象
    result = (*slObjectItf)->Realize(slObjectItf, SL_BOOLEAN_FALSE);
    if (result != SL_RESULT_SUCCESS)
        return;
    result = (*slObjectItf)->GetInterface(slObjectItf, SL_IID_ENGINE, &slEngineItf);
    if (result != SL_RESULT_SUCCESS)
        return;
    if (slEngineItf) {
        LOGI("get SLEngineItf success");
    } else {
        LOGI("get SLEngineItf failed");
    }
    /***********         1 创建引擎       ***************/

    /***********  2 创建混音器 ***************/
    SLObjectItf mixObjectItf = NULL;
    result = (*slEngineItf)->CreateOutputMix(slEngineItf, &mixObjectItf, 0, 0, 0);
    if (result != SL_RESULT_SUCCESS) {
        LOGE("CreateOutputMix failed");
        return;
    } else {
        LOGE("CreateOutputMix success");
    }

    //实例化混音器
    result = (*mixObjectItf)->Realize(mixObjectItf, SL_BOOLEAN_FALSE);
    if (result != SL_RESULT_SUCCESS) {
        LOGE("mixer init failed");
    } else {
        LOGI("mixer init success");
    }
    /***********  2 创建混音器 ***************/

    /***********  3 配置音频信息 ***************/

    SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, mixObjectItf};
    SLDataSink slDataSink = {&outputMix, 0};


    //缓冲队列
    SLDataLocator_AndroidSimpleBufferQueue queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 10};
    //音频格式
    SLDataFormat_PCM pcmFormat = {
            SL_DATAFORMAT_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
    };
    SLDataSource slDataSource = {&queue, &pcmFormat};
    /***********  3 配置音频信息 ***************/

    /************* 4 创建播放器 ****************/
    const SLInterfaceID ids[] = {SL_IID_BUFFERQUEUE};
    const SLboolean req[] = {SL_BOOLEAN_TRUE};
    SLObjectItf slPlayerItf = NULL;
    SLPlayItf slPlayItf;
    SLAndroidSimpleBufferQueueItf pcmQueue = NULL;
    result = (*slEngineItf)->CreateAudioPlayer(slEngineItf, &slPlayerItf, &slDataSource,
                                               &slDataSink, sizeof(ids) / sizeof(SLInterfaceID),
                                               ids, req);
    if (result != SL_RESULT_SUCCESS) {
        LOGE("create audio player failed");
    } else {
        LOGE("create audio player success");
    }
    //初始化播放器
    result = (*slPlayerItf)->Realize(slPlayerItf, SL_BOOLEAN_FALSE);
    if (result != SL_RESULT_SUCCESS) {
        LOGE("audio player init failed");
    } else {
        LOGE("audio player init success");
    }
    //获取player接口
    result = (*slPlayerItf)->GetInterface(slPlayerItf, SL_IID_PLAY, &slPlayItf);
    if (result != SL_RESULT_SUCCESS) {
        LOGE("player get SL_IID_PLAY failed");
    } else {
        LOGI("player get SL_IID_PLAY success");
    }

    //获取播放队列接口
    result = (*slPlayerItf)->GetInterface(slPlayerItf, SL_IID_BUFFERQUEUE, &pcmQueue);
    if (result != SL_RESULT_SUCCESS) {
        LOGE("player get SL_IID_BUFFERQUEUE failed");
    } else {
        LOGI("player get SL_IID_BUFFERQUEUE success");
    }
    /************* 4 创建播放器 ****************/

    //设置回调函数
    (*pcmQueue)->RegisterCallback(pcmQueue, CallBack, 0);
    //设置播放状态
    (*slPlayItf)->SetPlayState(slPlayItf, SL_PLAYSTATE_PLAYING);
    //启动队列
    (*pcmQueue)->Enqueue(pcmQueue, "", 1);

    env->ReleaseStringUTFChars(url_, url);
}

相关文章
|
2月前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
ijkplayer是由Bilibili基于FFmpeg3.4研发并开源的播放器,适用于Android和iOS,支持本地视频及网络流媒体播放。本文详细介绍如何在新版Android Studio中导入并使用ijkplayer库,包括Gradle版本及配置更新、导入编译好的so文件以及添加直播链接播放代码等步骤,帮助开发者顺利进行App调试与开发。更多FFmpeg开发知识可参考《FFmpeg开发实战:从零基础到短视频上线》。
229 2
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
|
2月前
|
编解码 语音技术 内存技术
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
《FFmpeg开发实战:从零基础到短视频上线》一书中的“5.1.2 把音频流保存为PCM文件”章节介绍了将媒体文件中的音频流转换为原始PCM音频的方法。示例代码直接保存解码后的PCM数据,保留了原始音频的采样频率、声道数量和采样位数。但在实际应用中,有时需要特定规格的PCM音频。例如,某些语音识别引擎仅接受16位PCM数据,而标准MP3音频通常采用32位采样,因此需将32位MP3音频转换为16位PCM音频。
89 0
FFmpeg开发笔记(五十八)把32位采样的MP3转换为16位的PCM音频
|
2月前
|
XML 开发工具 Android开发
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
ExoPlayer最初是为了解决Android早期MediaPlayer控件对网络视频兼容性差的问题而推出的。现在,Android官方已将其升级并纳入Jetpack的Media3库,使其成为音视频操作的统一引擎。新版ExoPlayer支持多种协议,解决了设备和系统碎片化问题,可在整个Android生态中一致运行。通过修改`build.gradle`文件、布局文件及Activity代码,并添加必要的权限,即可集成并使用ExoPlayer进行网络视频播放。具体步骤包括引入依赖库、配置播放界面、编写播放逻辑以及添加互联网访问权限。
189 1
FFmpeg开发笔记(五十六)使用Media3的Exoplayer播放网络视频
|
6月前
|
Java Linux
ffmpeg音频格式转换、合成、速率调整
ffmpeg音频格式转换、合成、速率调整
131 2
|
7月前
FFmpeg开发笔记(十八)FFmpeg兼容各种音频格式的播放
《FFmpeg开发实战》一书中,第10章示例程序playaudio.c原本仅支持mp3和aac音频播放。为支持ogg、amr、wma等非固定帧率音频,需进行三处修改:1)当frame_size为0时,将输出采样数量设为512;2)遍历音频帧时,计算实际采样位数以确定播放数据大小;3)在SDL音频回调函数中,确保每次发送len字节数据。改进后的代码在chapter10/playaudio2.c,可编译运行播放ring.ogg测试,成功则显示日志并播放铃声。
134 1
FFmpeg开发笔记(十八)FFmpeg兼容各种音频格式的播放
|
7月前
|
缓存 编解码
FFmpeg开发笔记(十四)FFmpeg音频重采样的缓存
FFmpeg在视频流重编码和音频重采样中使用缓存机制。在音频文件格式转换时,特别是对于帧长度不固定的格式如ogg、amr、wma,需处理重采样缓存。通过调用`swr_convert`,传入空输入和0大小来清空缓存。在`swrmp3.c`中,修改帧样本数处理,并在循环结束后添加代码以冲刷缓存。编译并运行程序,将ogg文件重采样为MP3,日志显示操作成功,播放转换后的文件确认功能正常。
154 7
FFmpeg开发笔记(十四)FFmpeg音频重采样的缓存
|
6月前
|
编解码 Python
音频剪裁大师:使用 Python 和 ffmpeg 分割音频的完整指南
使用 Python 和 ffmpeg 进行音频文件分割。通过 `subprocess` 模块调用 ffmpeg 命令,定义 `split_audio` 函数,输入参数包括音频文件、起始时间、持续时间和输出文件名。函数构建命令行指令进行分割,然后执行。运行脚本,即可按指定时间从音频中提取片段。简单易用,适用于多种音频处理场景。
|
7月前
|
编解码 计算机视觉 索引
使用ffmpeg MP4转 m3u8并播放 实测!!
使用ffmpeg MP4转 m3u8并播放 实测!!
367 1
|
7月前
|
网络协议 API 网络安全
探讨TCP传输视频流并利用FFmpeg进行播放的过程
探讨TCP传输视频流并利用FFmpeg进行播放的过程
721 0
|
2月前
|
Web App开发 安全 程序员
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
多年的互联网寒冬在今年尤为凛冽,坚守安卓开发愈发不易。面对是否转行或学习新技术的迷茫,安卓程序员可从三个方向进阶:1)钻研谷歌新技术,如Kotlin、Flutter、Jetpack等;2)拓展新功能应用,掌握Socket、OpenGL、WebRTC等专业领域技能;3)结合其他行业,如汽车、游戏、安全等,拓宽职业道路。这三个方向各有学习难度和保饭碗指数,助你在安卓开发领域持续成长。
85 1
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势