android平台OpenSL ES播放PCM数据

目录

  1. OpenSL ES是什么?
  2. 主要功能
  3. Android 平台的OpenSL ES
  4. 使用OpenSL ES 的优点
  5. API简要介绍
  6. 示例
  7. 参考

1. OpenSL ES是什么?

OpenSL ES(Open Sound Library for Embedded Systems,开源的嵌入式声音库)是一个免授权费、跨平台、C语言编写的适用于嵌入式系统的硬件加速音频库。它为移动和游戏行业的开发者提供标准化、高性能、低延迟的方法来实现音频功能,并致力于跨多个平台轻松移植应用程序。OpenSL ES由非营利性技术联盟Khronos Group管理。

OpenSL ES的设计目标是让应用程序开发人员能够访问高级音频功能,如3D定位音频和MIDI播放,同时努力在制造商和平台之间轻松实现应用程序移植。

简要来说 OpenSL ES 是一套针对嵌入式平台的音频功能API标准。

2. 主要功能

OpenSL ES主要功能包括:

  • 基本音频播放和录制。
  • 3D音频效果,包括3D定位音频。
  • 音乐体验增强效果,包括低音增强和环境混响。
  • 缓冲队列。

3. Android 平台的OpenSL ES

Android 2.3将OpenSL ES 1.0.1作为其NDK的一部分。在之后的版本中,实现的延迟有所改进。

Android 实现的 OpenSL ES 只是 OpenSL ES 1.0.1 的子集,并且进行了扩展。因此,对于 OpenSL ES API 的使用,我们需要特别留意哪些是 Android 支持的,哪些是不支持的。


android平台OpenSL ES播放PCM数据_第1张图片
image

不支持的功能:

  • 不支持 MIDI。
  • 不支持直接播放 DRM 或者 加密的内容。
  • 不支持音频数据的编解码,如需编解码,需要使用 MediaCodec API 或者第三方库。
  • 在音频延时方面,相比于JAVA的 API,并没有特别明显地改进。

4. 使用OpenSL ES 的优点

  • 相比于 Java API,避免音频数据频繁在 native 层和 Java 层拷贝,提高效率。
  • 相比于 Java API,可以更灵活地控制参数。
  • 使用 C 代码,可以做深度优化,比如采用 NEON 优化。

5. API简要介绍

OpenSL ES 虽然是 C 语言编写,但是它的接口采用的是面向对象的方式,并不是提供一系列的函数接口,而是以 Interface 的方式来提供 API,这是理解 OpenSL ES API 的一个比较重要的点。

它的大都数 API 需要这样访问:

//下面代码是对 Audio Engine 对象进行 “初始化”
SLEngineItf engineObject;
SLresult result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);

如果在 Android NDK 下开发过 C 代码,就应该不会太陌生,因为我们调用 “JNI* env” 的函数也是这个样子去调用的。

5.1 Object 和 Interface

OpenSL ES 有两个重要的概念 Object 和 Interface,“对象”和“接口”。
(1) 每个 Object 可能会存在一个或者多个 Interface,官方为每一种 Object 都定义了一系列的 Interface。
(2) 每个 Object 对象都提供了一些最基础的操作,比如:Realize,Resume,GetState,Destroy 等等,如果希望使用该对象支持的功能函数,则必须通过其 GetInterface 函数拿到 Interface 接口,然后通过 Interface 来访问功能函数。
(3) 并不是每个系统上都实现了 OpenSL ES 为 Object 定义的所有 Interface,所以在获取 Interface 的时候需要做一些选择和判断。

5.2 OpenSL ES的状态机制

OpenSL ES 有一个重要的概念:状态机制。如图所示:


android平台OpenSL ES播放PCM数据_第2张图片
OPENSL_ES_Object_state.PNG

任何一个 OpenSL ES 的对象,创建成功后,都进入 SL_OBJECT_STATE_UNREALIZED 状态,这种状态下,系统不会为它分配任何资源。

Realize 后的对象,就会进入 SL_OBJECT_STATE_REALIZED 状态,这是一种“可用”的状态,只有在这种状态下,对象的各个功能和资源才能正常地访问。

当一些系统事件发生后,比如出现错误或者 Audio 设备被其他应用抢占,OpenSL ES 对象会进入 SL_OBJECT_STATE_SUSPENDED 状态,如果希望恢复正常使用,需要调用 Resume 函数。

当调用对象的 Destroy 函数后,则会释放资源,并回到 SL_OBJECT_STATE_UNREALIZED 状态。

Engine对象是OpenSL ES API的入口点,这个对象使你能够创建OpenSL ES中所有其他对象。

Engine对象由全局的对象slCreateEngine()创建得到,创建的结果是Engine对象的一个SLObjectItf的接口。

5.3 Engine Object 和 SLEngineItf Interface

Engine Object是OpenSL ES 里面最核心的对象,
它主要提供如下两个功能:
(1) 管理 Audio Engine 的生命周期。
(2) 提供管理接口: SLEngineItf,该接口可以用来创建所有其他的 Object 对象。
(3) 提供设备属性查询接口:SLEngineCapabilitiesItf 和 SLAudioIODeviceCapabilitiesItf,这些接口可以查询设备的一些属性信息。

Engine Object 对象的创建和销毁的方法如下:

//创建
SLObjectItf engineObject;
slCreateEngine( &engineObject, 0, nullptr, 0, nullptr, nullptr );
//初始化
(*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
//销毁
(*engineObject)->Destroy(engineObject);

slCreateEngine的函数定义如下:

SLresult SLAPIENTRY slCreateEngine(
SLObjectItf *pEngine,
SLuint32 numOptions
constSLEngineOption *pEngineOptions,
SLuint32 numInterfaces,
constSLInterfaceID *pInterfaceIds,
constSLboolean *pInterfaceRequired
)

参数说明如下:
pEngine:指向输出的engine对象的指针。
numOptions:可选配置数组的大小。
pEngineOptions:可选配置数组。
numInterfaces:对象要求支持的接口数目,不包含隐含的接口。
pInterfaceId:对象需要支持的接口id的数组。
pInterfaceRequired:指定每个要求接口的接口是可选或者必须的标志位数组。如果要求的接口没有实现,创建对象会失败并返回错误码
SL_RESULT_FEATURE_UNSUPPORTED。

获取管理接口:

SLEngineItf engineEngine;
(*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &(engineEngine));

下面就可以使用 engineEngine 来创建所有 OpenSL ES 的其他对象了。

5.4 Media Object

Media Object代表着多媒体处理功能的抽象,如呈现和捕获媒体流的对象player、recorder 等等。

可以通过 SLEngineItf 提供的 CreateAudioPlayer 方法来创建一个 player 对象实例,可以通过 SLEngineItf 提供的 CreateAudioRecorder 方法来创建一个 recorder 实例。

5.5 Data Source 和 Data Sink

数据源(Data source)是媒体对象的输入参数,指定媒体对象将从何处接收特定类型的数据(例如采样的音频或MIDI数据)。 数据接收器(Data sink)是媒体对象的输入参数,指定媒体对象将发送特定类型数据的位置。

OpenSL ES 里面,这两个结构体均是作为创建 Media Object 对象时的参数而存在的,Data source 代表着输入源的信息,即数据从哪儿来、输入的数据参数是怎样的;而 Data Sink 代表着输出的信息,即数据输出到哪儿、以什么样的参数来输出。

Data Source 的定义如下:

typedef struct SLDataSource_ {
      void *pLocator;
      void *pFormat;
} SLDataSource;

Data Sink 的定义如下:

typedef struct SLDataSink_ {
    void *pLocator;
    void *pFormat;
} SLDataSink;

其中,pLocator 主要有如下几种:

SLDataLocator_Address
SLDataLocator_BufferQueue
SLDataLocator_IODevice
SLDataLocator_MIDIBufferQueue
SLDataLocator_URI

也就是说,Media Object 对象的输入源/输出源,既可以是 URL,也可以 Device,或者来自于缓冲区队列等等,完全是由 Media Object 对象的具体类型和应用场景来配置。

数据格式(data format)标识数据流的特征,包括以下几种类型:

  • 基于MIME类型的格式
  • PCM格式

5.6 Metadata Extractor Object

播放器对象支持读取媒体内容的元数据。但是有时候只是读取元数据而不播放媒体内容是很有用处的。
Metadata Extractor Object可以用于读取元数据而不需要分配用于媒体播放的资源。

5.7 示例说明

(1) 音频播放场景:


android平台OpenSL ES播放PCM数据_第3张图片
OPENSL_ES_playback_audio.PNG

使用了Audio Player对象来实现播放音频功能。使用engine对象的 SLEngineItf接口来创建Audio Player,创建之后与Output mix相关联用于音频输出。输入以URI作为示例,Output Mix默认与系统相关的默认输出设备关联。

(2) 录制音频场景:


android平台OpenSL ES播放PCM数据_第4张图片
OPENSL_ES_audio_recorder.PNG

通过Audio Recorder对象来实现音频录制功能。

6. 示例

OpenSL ES播放PCM数据主要有如下7个步骤:
1.创建EngineObject
2.设置DataSource
3.设置DataSink
4.创建播放器
5.设置缓冲队列和回调函数
6.设置播放状态
7.启动回调函数

6.1 创建接口对象

SLresult OpenGLESPlayer::createEngine() {
    LOGD("createEngine()"); 
    SLresult result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
    if(result != SL_RESULT_SUCCESS) {
        LOGD("slCreateEngine failed, result=%d", result);
        return result;
    }
    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    if(result != SL_RESULT_SUCCESS) {
        LOGD("engineObject Realize failed, result=%d", result);
        return result;
    }
    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &(engineEngine));
    if(result != SL_RESULT_SUCCESS) {
        LOGD("engineObject GetInterface failed, result=%d", result);
        return result;
    }
    return result;
}

6.2 设置混音器

    // set DataSource
    SLDataLocator_AndroidSimpleBufferQueue android_queue={SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,2};
    SLDataFormat_PCM sLDataFormat_pcm={
            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 = {&android_queue, &sLDataFormat_pcm};

6.3 设置DataSink

    //set DataSink
    const SLInterfaceID mids[1] = {SL_IID_ENVIRONMENTALREVERB};
    const SLboolean mreq[1] = {SL_BOOLEAN_FALSE};
    ret = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, mids, mreq);
    if(ret != SL_RESULT_SUCCESS) {
        LOGD("CreateOutputMix failed, ret=%d", ret);
        return ret;
    }
    ret = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    if(ret != SL_RESULT_SUCCESS) {
        LOGD("Realize failed, result=%d", ret);
        return ret;
    }
    SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    SLDataSink audioSnk = {&outputMix, NULL};

6.4 创建播放器

    const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};
    const SLboolean req[1] = {SL_BOOLEAN_TRUE};
    ret = (*engineEngine)->CreateAudioPlayer(engineEngine, &playerObject, &slDataSource, &audioSnk, 1, ids, req);
    if (ret != SL_RESULT_SUCCESS) {
        LOGD("CreateAudioPlayer() failed.");
        return ret;
    }
    ret = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
    if (ret != SL_RESULT_SUCCESS) {
        LOGD("playerObject Realize() failed.");
        return ret;
    }
    ret = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playerPlay);
    if (ret != SL_RESULT_SUCCESS) {
        LOGD("playerObject GetInterface(SL_IID_PLAY) failed.");
        return ret;
    }

6.5 设置缓冲队列和回调函数

    ret = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE, &simpleBufferQueue);
    if (ret != SL_RESULT_SUCCESS) {
        LOGD("playerObject GetInterface(SL_IID_BUFFERQUEUE) failed.");
        return ret;
    }

    ret = (*simpleBufferQueue)->RegisterCallback(simpleBufferQueue, pcmBufferCallBack, this);
    if (ret != SL_RESULT_SUCCESS) {
        LOGD("SLAndroidSimpleBufferQueueItf RegisterCallback() failed.");
        return ret;
    }
    return ret;
int64_t getPcmData(void **pcm, FILE *pcmFile, uint8_t *out_buffer) {
    while(!feof(pcmFile)) {
        size_t size = fread(out_buffer, 1, 44100 * 2 * 2, pcmFile);
        *pcm = out_buffer;
        return size;
    }
    return 0;
}

void pcmBufferCallBack(SLAndroidSimpleBufferQueueItf bf, void * context) {
    int32_t size = getPcmData(&buffer, pcmFile, out_buffer);
    LOGD("pcmBufferCallBack, size=%d", size);
    if (NULL != buffer && size > 0) {
        SLresult result = (*simpleBufferQueue)->Enqueue(simpleBufferQueue, buffer, size);
    }
}

6.6 设置播放状态

void OpenGLESPlayer::start() {
    (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
//    主动调用回调函数开始工作
    pcmBufferCallBack(simpleBufferQueue, this);
}

void OpenGLESPlayer::stop() {
    LOGD("stop");
    (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_STOPPED);
}

6.7 启动回调函数

void OpenGLESPlayer::start() {
    (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
//    主动调用回调函数开始工作
    pcmBufferCallBack(simpleBufferQueue, this);
}

完整示例:Github

7.参考阅读

  1. opensles官网
  2. opensels wikipedia
  3. android-audio-high-performance/guides
  4. native-audio github
  5. Android* Low-Latency Audio on x86-based Mobile Devices
  6. OpenSL_ES_Specification_1.0
  7. 使用 OpenSL ES API
  8. MIDI百度百科
  9. OpenSLES_Android.h 源码
  10. Android通过OpenSL ES播放音频套路详解

你可能感兴趣的:(android平台OpenSL ES播放PCM数据)