(六) OpenSLES音频播放

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huanghuangjin/article/details/81948994

OpenSL ES 播放声音的流程:
这里写图片描述
这里写图片描述

代码

#include "common.hpp"
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>

static SLObjectItf slObject = NULL; // 引擎的上下文

static SLEngineItf createSLEngine()
{
    SLresult ret;
    SLEngineItf en; // SLEngineItf 本身是指针,不用担心局部变量内存被回收了
    /*
        SL_API SLresult SLAPIENTRY slCreateEngine( // 创建OpenSLES引擎
            SLObjectItf             *pEngine, // 上下文
            SLuint32                numOptions, // 选择项的数量
            const SLEngineOption    *pEngineOptions, // 具体的选择项
            SLuint32                numInterfaces, // 支持的接口的数量
            const SLInterfaceID     *pInterfaceIds, // 具体的要支持的接口,是枚举的数组
            const SLboolean         * pInterfaceRequired // 具体的要支持的接口是开放的还是关闭的,也是一个数组,这三个参数长度是一致的
        );
     */
    ret = slCreateEngine(&slObject, 0, NULL, 0, NULL, 0);
    if (ret!=SL_RESULT_SUCCESS)
    {
        LOGE("slCreateEngine failed.");
        return NULL;
    }
    /*
        SLresult (*Realize) ( // 实例化,将内部的存储做好
            SLObjectItf self, // 相当于上下文
            SLboolean async // SL_BOOLEAN_FALSE 表示阻塞知道初始化完成,设置为 SL_BOOLEAN_TURE 时立即返回成功,这个时候初始化的执行是异步的
        );
     */
    ret = (*slObject)->Realize(slObject, SL_BOOLEAN_FALSE);
    if (ret!=SL_RESULT_SUCCESS)
    {
        LOGE("(*slEngine)->Realize failed.");
        return NULL;
    }
    /*
        SLresult (*GetInterface) ( // 获取接口
            SLObjectItf self, // 上下文
            const SLInterfaceID iid, // 要获取的接口类型
            void * pInterface // 获取到的接口存储到这里面
        );
     */
    ret = (*slObject)->GetInterface(slObject, SL_IID_ENGINE, &en); // SL_IID_ENGINE 为要获取的具体的接口,是一个对象,不是宏,获取后存放到 en 中
    if (ret!=SL_RESULT_SUCCESS)
    {
        LOGE("(*slEngine)->GetInterface SL_IID_ENGINE failed.");
        return NULL;
    }
    return en;
}

// 缓冲队列接口的回调函数,在播放队列为空的时候调用, 要确保此函数中的代码能快速执行完,如果此函数处理速度很慢,会造成播放声音时,听起来感觉声音断掉了
static void pcmCall(SLAndroidSimpleBufferQueueItf bf, void *contex)
{
    static FILE * fp = NULL;
    static char * buf = NULL;
    if (!fp)
    {
        char * path = (char *) contex;
        LOGD("path=%s", path);
        fp = fopen(path, "rb");
        buf = new char[1024*1024];
    }
    if (!fp)
    {
        LOGE("打开音频文件失败.");
        delete [] buf;
        return;
    }
    if (feof(fp) != EOF)
    {
        int len = fread(buf, 1, 1024, fp); // 读1024次,每次读1个字节
        if (len>0)
        {
            (*bf)->Enqueue(bf, buf, len); // 给队列传递数据
        }
    }
    else
    {
        fclose(fp);
        delete [] buf;
    }
}

JNIEXPORT void JNICALL Java_hankin_hjmedia_mpeg_Mp7_11Activity_play(JNIEnv *env, jobject instance, jstring path_)
{
    const char * path = env->GetStringUTFChars(path_, NULL);

    // 1、创建OpenSLES引擎
    SLEngineItf eng = createSLEngine();
    if (eng==NULL) return;
    // 2、创建混音器,即创建输出设备的信息
    SLObjectItf mix = NULL;
    /*
        SLresult (*CreateOutputMix) ( // 创建混音器
            SLEngineItf self, // 引擎
            SLObjectItf * pMix, // 混音器,输入输出参数
            SLuint32 numInterfaces, // 支持的接口的数量
            const SLInterfaceID * pInterfaceIds, // 具体的要支持的接口,是枚举的数组
            const SLboolean * pInterfaceRequired // 具体的要支持的接口是开放的还是关闭的,也是一个数组,这三个参数长度是一致的
        );
     */
    SLresult ret = (*eng)->CreateOutputMix(eng, &mix, 0, 0, 0); // 后面的参数是做音效特效的
    if (ret!=SL_RESULT_SUCCESS)
    {
        LOGE("(*eng)->CreateOutputMix failed.");
        return;
    }
    /*
        SLresult (*Realize) ( // 实例化混音器
            SLObjectItf self, // 混音器本身
            SLboolean async // SL_BOOLEAN_FALSE 表示阻塞知道初始化完成,设置为 SL_BOOLEAN_TURE 时立即返回成功,这个时候初始化的执行是异步的
        );
     */
    ret = (*mix)->Realize(mix, SL_BOOLEAN_FALSE); // SL_BOOLEAN_FALSE 表示阻塞等待混音器创建完毕
    if (ret!=SL_RESULT_SUCCESS)
    {
        LOGE("(*mix)->Realize failed.");
        return;
    }
    SLDataLocator_OutputMix outmix = {SL_DATALOCATOR_OUTPUTMIX, mix}; // SLDataLocator_OutputMix 表示输出的对象,SL_DATALOCATOR_OUTPUTMIX是固定的传这个
    SLDataSink audioSink = {&outmix, NULL}; // audioSink 表示声音的对象,播放的时候传进去,表示播放的是哪个混音设备
    // 3、配置输入的音频的信息(数据源是pcm)
    // 缓冲队列的配置信息,用队列的方式在cpu与音频播放设备之间来交互,SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE 是固定传入,10表示队列的长度
    SLDataLocator_AndroidSimpleBufferQueue que = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 10};
    SLDataFormat_PCM pcm = { // 输入的音频格式,已知的 test.pcm 就是以下配置
            SL_DATAFORMAT_PCM, // 输入的音频格式,这里只能是SL_DATAFORMAT_PCM,表示PCM格式的原始未压缩的数据,其他格式的用其他格式对应的结构体?
            2, // 2个声道数(立体声)
            SL_SAMPLINGRATE_44_1, // 采样率 44100hz
            SL_PCMSAMPLEFORMAT_FIXED_16, // 16bit保存一个音
            SL_PCMSAMPLEFORMAT_FIXED_16, // 容器大小,与 bitsPerSample 一样大可以了
            SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, // 前左声道与前右声道,  左右声道(立体声)
            SL_BYTEORDER_LITTLEENDIAN // 字节序, SL_BYTEORDER_LITTLEENDIAN 表示小端 ,播放的声音有问题的时候,这个是一个排查的方向
    };
    SLDataSource ds = {&que, &pcm}; // 数据源信息的结构体,创建播放器的时候使用
    // 4、创建播放器
    SLObjectItf player = NULL; // 播放器
    SLPlayItf playerItf = NULL; // 播放器的接口
    SLAndroidSimpleBufferQueueItf pcmQueItf = NULL; // 缓冲队列的接口,通过此接口往队列写数据,发送的数据播放完后会从缓冲队列中删掉
    const SLInterfaceID ids[] = {SL_IID_BUFFERQUEUE}; // SL_IID_BUFFERQUEUE 表示缓冲队列的接口
    const SLboolean req[] = {SL_BOOLEAN_TRUE}; // 表示ids中需要获取的接口是开放还是关闭,这两个数组要长度一致
    /*
        SLresult (*CreateAudioPlayer) ( // 创建播放器
            SLEngineItf self, // 引擎
            SLObjectItf * pPlayer, // 创建的播放器,输入输出参数
            SLDataSource *pAudioSrc, // 数据源的配置信息
            SLDataSink *pAudioSnk, // 输出的设备的配置信息
            SLuint32 numInterfaces, // 支持的接口的数量
            const SLInterfaceID * pInterfaceIds, // 具体的要支持的接口,是枚举的数组
            const SLboolean * pInterfaceRequired // 具体的要支持的接口是开放的还是关闭的,也是一个数组,这三个参数长度是一致的
        );
     */
    ret = (*eng)->CreateAudioPlayer(eng, &player, &ds, &audioSink, sizeof(ids)/sizeof(SLInterfaceID), ids, req);
    if (ret!=SL_RESULT_SUCCESS)
    {
        LOGE("(*eng)->CreateAudioPlayer failed.");
        return;
    }
    (*player)->Realize(player, SL_BOOLEAN_FALSE); // 阻塞等待实例化完成
    /*
        SLresult (*GetInterface) ( // 获取接口
            SLObjectItf self, // 上下文
            const SLInterfaceID iid, // 要获取的接口类型
            void * pInterface // 获取到的接口存储到这里面
        );
     */
    ret = (*player)->GetInterface(player, SL_IID_PLAY, &playerItf); // SL_IID_PLAY 表示要获取播放接口,CreateAudioPlayer函数中不需要设置支持它
    if (ret!=SL_RESULT_SUCCESS)
    {
        LOGE("(*player)->GetInterface SL_IID_PLAY failed.");
        return;
    }
    ret = (*player)->GetInterface(player, SL_IID_BUFFERQUEUE, &pcmQueItf); // 获取播放的缓冲队列接口,用于注册缓冲队列接口的回调函数
    if (ret!=SL_RESULT_SUCCESS)
    {
        LOGE("(*player)->GetInterface SL_IID_BUFFERQUEUE failed.");
        return;
    }
    /*
        SLresult (*RegisterCallback) ( // 设置缓冲接口的回调函数,在播放队列为空的时候调用
            SLAndroidSimpleBufferQueueItf self,
            slAndroidSimpleBufferQueueCallback callback, // 回调函数
            void* pContext // 给回调函数传的参数,看情况设置
        );
     */
    (*pcmQueItf)->RegisterCallback(pcmQueItf, pcmCall, (void *) path); // env->ReleaseStringUTFChars(path_, path); 可能会在回调函数前调用,需要注意
    /*
        SLresult (*SetPlayState) ( // 设置播放状态
            SLPlayItf self, // 播放接口
            SLuint32 state // 播放的状态,有 SL_PLAYSTATE_PLAYING SL_PLAYSTATE_PAUSED SL_PLAYSTATE_STOPPED 三种状态
        );
     */
    (*playerItf)->SetPlayState(playerItf, SL_PLAYSTATE_PLAYING); // 设置播放状态,播放中,这个时候播放队列的回调函数并不会被调用
    /*
        SLresult (*Enqueue) ( // 给队列传递数据
            SLAndroidSimpleBufferQueueItf self, // 队列
            const void *pBuffer, // 要传送的数据
            SLuint32 size // 数据的长度
        );
     */
    (*pcmQueItf)->Enqueue(pcmQueItf, "", 1); // 调用一次播放队列回调函数, "", 1 表示传递了一个 \0 ,\0在声音中表示无声

    /*
        void (*Destroy) ( // 释放内存,播放音频的内存为全局变量只会创建一次,而且要贯穿应用进程,所以可以不用考虑释放的问题
            SLObjectItf self
        );
     */
//    (*slObject)->Destroy(slObject);
//    env->ReleaseStringUTFChars(path_, path);
}

猜你喜欢

转载自blog.csdn.net/huanghuangjin/article/details/81948994