Android音视频OpenSL ES音频播放示例一

Posted Taozi825232603

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android音视频OpenSL ES音频播放示例一相关的知识,希望对你有一定的参考价值。

本文将实现一个使用OpenSL ES来播放assets目录下mp3歌曲的demo(实际推荐大家使用oboe库)。

android NDK之高性能音频https://developer.android.google.cn/ndk/guides/audio/opensl/getting-started

Oboe is a C++ library that makes it easy to build high-performance audio apps on Android.https://github.com/google/oboe NDK 软件包中包括 Khronos Group 开发的 OpenSL ES™ 1.0.1 API 规范的 Android 专用实现。利用这个库,不论您是编写合成器、数字音频工作站、卡拉 OK 应用、游戏还是其他实时应用,都可以使用 C 或 C++ 实现高性能、低延迟的音频。

OpenSL ES™ 标准与 Android Java 框架中的 MediaPlayer 和 MediaRecorder API 提供类似的音频功能。

OpenSL ES API 可以帮助您开发和提升应用的音频性能。

标准 OpenSL ES 头文件 <SLES/OpenSLES.h> 和 <SLES/OpenSLES_Platform.h> 允许音频输入和输出。<SLES/OpenSLES_Android.h> 和 <SLES/OpenSLES_AndroidConfiguration.h> 中提供了其他 Android 专用功能。

OpenSL ES编程简述https://developer.android.google.cn/ndk/guides/audio/opensl/opensl-prog-notes


PlayerX.c

#include <stdlib.h>
#include <assert.h>
#include <jni.h>
#include <string.h>

#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>

#include <sys/types.h>
#include <android/asset_manager.h>
#include <android/asset_manager_jni.h>

该示例将包含4个简单的接口

  • 引擎初始化(学习 SLObjectItf,SLEngineItf)
  • 创建播放器(学习 SLPlayItf)
  • 切换播放状态
  • 回收资源
static SLObjectItf engineObject = NULL;
static SLEngineItf engineEngine = NULL;

static SLObjectItf outputMixObject = NULL;
static SLEnvironmentalReverbItf outputMixEnvironmentalReverb = NULL;

JNIEXPORT void JNICALL
Java_tao_h_playerx_MainActivity_createEngine(JNIEnv *env, jclass clazz) 
    SLresult result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;

    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;

    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;

    const SLInterfaceID ids[1] = SL_IID_ENVIRONMENTALREVERB;
    const SLboolean req[1] = SL_BOOLEAN_FALSE;
    result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids, req);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;

    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;

    result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB,
                                              &outputMixEnvironmentalReverb);
    const SLEnvironmentalReverbSettings reverbSettings = SL_I3DL2_ENVIRONMENT_PRESET_STONECORRIDOR;
    if (result == SL_RESULT_SUCCESS) 
        result = (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(
                outputMixEnvironmentalReverb, &reverbSettings);
        (void) result;
    

 (void)result 只是用于忽略编译警告


创建基于FD的音频播放器

static SLObjectItf fdPlayerObject = NULL;
static SLPlayItf fdPlayerPlay = NULL;
static SLSeekItf fdPlayerSeek = NULL;

JNIEXPORT jboolean JNICALL
Java_tao_h_playerx_MainActivity_createAssetAudioPlayer(JNIEnv *env, jclass clazz,
                                                       jobject asset_manager, jstring file_name) 
    SLresult result;

    const char *utf8 = (*env)->GetStringUTFChars(env, file_name, NULL);
    assert(NULL != utf8);

    AAssetManager *mgr = AAssetManager_fromJava(env, asset_manager);
    assert(NULL != mgr);
    AAsset *asset = AAssetManager_open(mgr, utf8, AASSET_MODE_UNKNOWN);

    (*env)->ReleaseStringUTFChars(env, file_name, utf8);

    if (asset == NULL) 
        return JNI_FALSE;
    

    off_t start, length;
    int fd = AAsset_openFileDescriptor(asset, &start, &length);
    assert(0 <= fd);
    AAsset_close(asset);

    SLDataLocator_AndroidFD loc_fd = SL_DATALOCATOR_ANDROIDFD, fd, start, length;
    SLDataFormat_MIME format_mime = SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_UNSPECIFIED;
    SLDataSource audiosrc = &loc_fd, &format_mime;

    SLDataLocator_OutputMix loc_output_mix = SL_DATALOCATOR_OUTPUTMIX, outputMixObject;
    SLDataSink audioSink = &loc_output_mix, NULL;

    const SLInterfaceID ids[3] = SL_IID_SEEK, SL_IID_MUTESOLO, SL_IID_VOLUME;
    const SLboolean req[3] = SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE;
    result = (*engineEngine)->CreateAudioPlayer(engineEngine, &fdPlayerObject, &audioSrc,
                                                &audioSink, 3, ids, req);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;

    result = (*fdPlayerObject)->Realize(fdPlayerObject, SL_BOOLEAN_FALSE);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;

    result = (*fdPlayerObject)->GetInterface(fdPlayerObject, SL_IID_PLAY, &fdPlayerPlay);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;

    result = (*fdPlayerObject)->GetInterface(fdPlayerObject, SL_IID_SEEK, &fdPlayerSeek);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;

    result = (*fdPlayerSeek)->SetLoop(fdPlayerSeek, SL_BOOLEAN_TRUE, 0, SL_TIME_UNKNOWN);
    assert(SL_RESULT_SUCCESS == result);
    (void) result;

    return JNI_TRUE;

设置播放状态

JNIEXPORT void JNICALL
Java_tao_h_playerx_MainActivity_setPlayingAssetAudioPlayer(JNIEnv *env, jclass clazz,
                                                           jboolean is_playing) 
    SLresult result;

    if (NULL != fdPlayerPlay) 
        result = (*fdPlayerPlay)->SetPlayState(fdPlayerPlay, is_playing ? SL_PLAYSTATE_PLAYING
                                                                        : SL_PLAYSTATE_PAUSED);
        assert(SL_RESULT_SUCCESS == result);
        (void) result;
    

 回收资源(务必按照与创建时相反的顺序进行资源释放)

JNIEXPORT void JNICALL
Java_tao_h_playerx_MainActivity_shutdown(JNIEnv *env, jclass clazz) 
    if (fdPlayerObject != NULL) 
        (*fdPlayerObject)->Destroy(fdPlayerObject);
        fdPlayerPlay = NULL;
        fdPlayerSeek = NULL;
    

    if (outputMixObject != NULL) 
        (*outputMixObject)->Destroy(outputMixObject);
        outputMixObject = NULL;
        outputMixEnvironmentalReverb = NULL;
    

    if (engineObject != NULL) 
        (*engineObject)->Destroy(engineObject);
        engineObject = NULL;
        engineEngine = NULL;
    

 创建简单的页面来进行播放,布局就不放上来了

public class MainActivity extends AppCompatActivity 

    static 
        System.loadLibrary("PlayerX");
    

    private AssetManager mAssetManager;
    private boolean isPlayingAsset = false;

    public static native void createEngine();

    public static native boolean createAssetAudioPlayer(AssetManager assetManager, String fileName);

    public static native void setPlayingAssetAudioPlayer(boolean isPlaying);

    public static native void shutdown();

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mAssetManager = getAssets();

        createEngine();

        findViewById(R.id.asset_song).setOnClickListener(new View.OnClickListener() 
            boolean created = false;

            @Override
            public void onClick(View v) 
                if (!created) 
                    created = createAssetAudioPlayer(mAssetManager, "Legend Of Heroes.mp3");
                

                if (created) 
                    isPlayingAsset = !isPlayingAsset;
                    setPlayingAssetAudioPlayer(isPlayingAsset);
                
            
        );
    

    @Override
    protected void onDestroy() 
        shutdown();
        super.onDestroy();
    

CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1)

project("PlayerX")

set(CMAKE_C_FLAGS "$CMAKE_C_FLAGS -std=c99 -Wall")

add_library(PlayerX
        SHARED
        PlayerX.c)

target_link_libraries(PlayerX
        android
        log
        OpenSLES)

Android音视频学习第7章:使用OpenSL ES进行音频解码

这里写图片描述
/*
*
*这里使用了transcode-1.1.7对wav文件进行解码,然后使用opensl es进行播放
*
*/


//用到的变量和结构体

WAV wav; //wav文件指针
SLObjectItf engineObject; //引擎对象
SLEngineItf engineInterface; //引擎接口
SLObjectItf outputMixObject; //混音器
SLObjectItf audioPlayerObject; //播放器对象
SLAndroidSimpleBufferQueueItf andioPlayerBufferQueueItf;    //缓冲器队列接口
SLPlayItf audioPlayInterface;   //播放接口


unsigned char *buffer; //缓冲区
size_t bufferSize;     //缓冲区大小

//上下文
struct PlayerContext{
    WAV wav;
    unsigned char *buffer;
    size_t bufferSize;

    PlayerContext(WAV wav,
            unsigned char *buffer,
            size_t bufferSize){
        this->wav = wav;
        this->buffer = buffer;
        this->bufferSize = bufferSize;
    }
};
//实现对象
void RealizeObject(SLObjectItf object){
    //非异步(阻塞)
    (*object)->Realize(object,SL_BOOLEAN_FALSE);
}

具体实现流程:

1.打开文件

WAV wav = OpenWaveFile(env,jFileName);
//打开文件
WAV OpenWaveFile(JNIEnv *env,jstring jFileName){
    const char *cFileName = env->GetStringUTFChars(jFileName,JNI_FALSE);
    WAVError err;
    WAV wav = wav_open(cFileName,WAV_READ,&err);

    LOGI("%d",wav_get_bitrate(wav));
    env->ReleaseStringUTFChars(jFileName,cFileName);
    if(wav == 0){
        LOGE("%s",wav_strerror(err));
    }
    return wav;
}

2.创建OpenSL ES引擎

//OpenSL ES在Android平台下默认是线程安全的,这样设置是为了为了兼容其他平台
    SLEngineOption options[] = {
        {(SLuint32)SL_ENGINEOPTION_THREADSAFE, (SLuint32)SL_BOOLEAN_TRUE}
    };
    slCreateEngine(&engineObject,ARRAY_LEN(engineObject),options,0,0,0); //没有接口

//实例化对象
    //对象创建之后,处于未实例化状态,对象虽然存在但未分配任何资源,使用前先实例化(使用完之后destroy)
    RealizeObject(engineObject);

3.获取引擎接口

(*engineObject)->GetInterface(engineObject,SL_IID_ENGINE,&engineInterface);

4.创建输出混音器

(*engineInterface)->CreateOutputMix(engineInterface,&outputMixObject,0,0,0); //没有接口
    //实例化混音器
    RealizeObject(outputMixObject);

5.创建缓冲区保存读取到的音频数据库

//缓冲区的大小
    bufferSize = wav_get_channels(wav) * wav_get_rate(wav) * wav_get_bits(wav);
    buffer = new unsigned char[bufferSize];

6.创建带有缓冲区队列的音频播放器

CreateBufferQueueAudioPlayer(wav,engineInterface,outputMixObject,audioPlayerObject);
    //实例化音频播放器
    RealizeObject(audioPlayerObject);

CreateBufferQueueAudioPlayer.cpp

extern "C" {
#include "wavlib.h"
}
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <android/log.h>

#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
//创建音频播放对象
void CreateBufferQueueAudioPlayer(
        WAV wav,
        SLEngineItf engineEngine,
        SLObjectItf outputMixObject,
        SLObjectItf &audioPlayerObject){

    // Android针对数据源的简单缓冲区队列定位器
    SLDataLocator_AndroidSimpleBufferQueue dataSourceLocator = {
        SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, // 定位器类型
        1                                        // 缓冲区数
    };

    // PCM数据源格式
    SLDataFormat_PCM dataSourceFormat = {
        SL_DATAFORMAT_PCM,        // 格式类型
        wav_get_channels(wav),    // 通道数
        wav_get_rate(wav) * 1000, // 毫赫兹/秒的样本数
        wav_get_bits(wav),        // 每个样本的位数
        wav_get_bits(wav),        // 容器大小
        SL_SPEAKER_FRONT_CENTER,  // 通道屏蔽
        SL_BYTEORDER_LITTLEENDIAN // 字节顺序
    };

    // 数据源是含有PCM格式的简单缓冲区队列
    SLDataSource dataSource = {
        &dataSourceLocator, // 数据定位器
        &dataSourceFormat   // 数据格式
    };

    // 针对数据接收器的输出混合定位器
    SLDataLocator_OutputMix dataSinkLocator = {
        SL_DATALOCATOR_OUTPUTMIX, // 定位器类型
        outputMixObject           // 输出混合
    };

    // 数据定位器是一个输出混合
    SLDataSink dataSink = {
        &dataSinkLocator, // 定位器
        0                 // 格式
    };

    // 需要的接口
    SLInterfaceID interfaceIds[] = {
        SL_IID_BUFFERQUEUE
    };

    // 需要的接口,如果所需要的接口不要用,请求将失败
    SLboolean requiredInterfaces[] = {
        SL_BOOLEAN_TRUE // for SL_IID_BUFFERQUEUE
    };

    // 创建音频播放器对象
    SLresult result = (*engineEngine)->CreateAudioPlayer(
            engineEngine,
            &audioPlayerObject,
            &dataSource,
            &dataSink,
            ARRAY_LEN(interfaceIds),
            interfaceIds,
            requiredInterfaces);
}

7.获得缓冲区队列接口Buffer Queue Interface

//通过缓冲区队列接口对缓冲区进行排序播放
    (*audioPlayerObject)->GetInterface(audioPlayerObject,SL_IID_BUFFERQUEUE,&andioPlayerBufferQueueItf);

8.注册音频播放器回调函数

//当播放器完成对前一个缓冲区队列的播放时,回调函数会被调用,然后我们又继续读取音频数据,直到结束
    //上下文,包裹参数方便再回调函数中使用
    PlayerContext *ctx = new PlayerContext(wav,buffer,bufferSize);
    (*andioPlayerBufferQueueItf)->RegisterCallback(andioPlayerBufferQueueItf,PlayerCallBack,ctx);
//回调函数
void PlayerCallBack(SLAndroidSimpleBufferQueueItf andioPlayerBufferQueue,void *context){
    PlayerContext* ctx = (PlayerContext*)context;
    //读取数据
    ssize_t readSize = wav_read_data(ctx->wav,ctx->buffer,ctx->bufferSize);
    if(0 < readSize){
        (*andioPlayerBufferQueue)->Enqueue(andioPlayerBufferQueue,ctx->buffer,readSize);
    }else{
        //destroy context
        CloseWaveFile(ctx->wav); //关闭文件
        delete ctx->buffer; //释放缓存
    }
}

9.获取Play Interface通过对SetPlayState函数来启动播放音乐

//一旦播放器被设置为播放状态,该音频播放器开始等待缓冲区排队就绪
    (*audioPlayerObject)->GetInterface(audioPlayerObject,SL_IID_PLAY,&audioPlayInterface);
    //设置播放状态
    (*audioPlayInterface)->SetPlayState(audioPlayInterface,SL_PLAYSTATE_PLAYING);

10.开始,让第一个缓冲区入队

这里写图片描述

PlayerCallBack(andioPlayerBufferQueueItf,ctx);

完整代码

#include "com_dongnaoedu_jasonaudioplayer_AudioPlayer.h"

extern "C" {
#include "wavlib.h"
}
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <android/log.h>
#include "CreateBufferQueueAudioPlayer.cpp"

#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"jason",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"jason",FORMAT,##__VA_ARGS__);


#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))


WAV wav; //wav文件指针
SLObjectItf engineObject; //引擎对象
SLEngineItf engineInterface; //引擎接口
SLObjectItf outputMixObject; //混音器
SLObjectItf audioPlayerObject; //播放器对象
SLAndroidSimpleBufferQueueItf andioPlayerBufferQueueItf;    //缓冲器队列接口
SLPlayItf audioPlayInterface;   //播放接口


unsigned char *buffer; //缓冲区
size_t bufferSize;     //缓冲区大小

//上下文
struct PlayerContext{
    WAV wav;
    unsigned char *buffer;
    size_t bufferSize;

    PlayerContext(WAV wav,
            unsigned char *buffer,
            size_t bufferSize){
        this->wav = wav;
        this->buffer = buffer;
        this->bufferSize = bufferSize;
    }
};

//打开文件
WAV OpenWaveFile(JNIEnv *env,jstring jFileName){
    const char *cFileName = env->GetStringUTFChars(jFileName,JNI_FALSE);
    WAVError err;
    WAV wav = wav_open(cFileName,WAV_READ,&err);

    LOGI("%d",wav_get_bitrate(wav));
    env->ReleaseStringUTFChars(jFileName,cFileName);
    if(wav == 0){
        LOGE("%s",wav_strerror(err));
    }
    return wav;
}

//关闭文件
void CloseWaveFile(WAV wav){
    wav_close(wav);
}

//实现对象
void RealizeObject(SLObjectItf object){
    //非异步(阻塞)
    (*object)->Realize(object,SL_BOOLEAN_FALSE);
}

//回调函数
void PlayerCallBack(SLAndroidSimpleBufferQueueItf andioPlayerBufferQueue,void *context){
    PlayerContext* ctx = (PlayerContext*)context;
    //读取数据
    ssize_t readSize = wav_read_data(ctx->wav,ctx->buffer,ctx->bufferSize);
    if(0 < readSize){
        (*andioPlayerBufferQueue)->Enqueue(andioPlayerBufferQueue,ctx->buffer,readSize);
    }else{
        //destroy context
        CloseWaveFile(ctx->wav); //关闭文件
        delete ctx->buffer; //释放缓存
    }
}

JNIEXPORT void JNICALL Java_com_dongnaoedu_jasonaudioplayer_AudioPlayer_play
  (JNIEnv *env, jclass jthiz, jstring jFileName){
    //1.打开文件
    WAV wav = OpenWaveFile(env,jFileName);

    //2.创建OpenSL ES引擎
    //OpenSL ES在Android平台下默认是线程安全的,这样设置是为了为了兼容其他平台
    SLEngineOption options[] = {
        {(SLuint32)SL_ENGINEOPTION_THREADSAFE, (SLuint32)SL_BOOLEAN_TRUE}
    };
    slCreateEngine(&engineObject,ARRAY_LEN(engineObject),options,0,0,0); //没有接口

    //实例化对象
    //对象创建之后,处于未实例化状态,对象虽然存在但未分配任何资源,使用前先实例化(使用完之后destroy)
    RealizeObject(engineObject);

    //3.获取引擎接口
    (*engineObject)->GetInterface(engineObject,SL_IID_ENGINE,&engineInterface);

    //4.创建输出混音器
    (*engineInterface)->CreateOutputMix(engineInterface,&outputMixObject,0,0,0); //没有接口

    //实例化混音器
    RealizeObject(outputMixObject);

    //5.创建缓冲区保存读取到的音频数据库
    //缓冲区的大小
    bufferSize = wav_get_channels(wav) * wav_get_rate(wav) * wav_get_bits(wav);
    buffer = new unsigned char[bufferSize];

    //6.创建带有缓冲区队列的音频播放器
    CreateBufferQueueAudioPlayer(wav,engineInterface,outputMixObject,audioPlayerObject);

    //实例化音频播放器
    RealizeObject(audioPlayerObject);

    //7.获得缓冲区队列接口Buffer Queue Interface
    //通过缓冲区队列接口对缓冲区进行排序播放
    (*audioPlayerObject)->GetInterface(audioPlayerObject,SL_IID_BUFFERQUEUE,&andioPlayerBufferQueueItf);

    //8.注册音频播放器回调函数
    //当播放器完成对前一个缓冲区队列的播放时,回调函数会被调用,然后我们又继续读取音频数据,直到结束
    //上下文,包裹参数方便再回调函数中使用
    PlayerContext *ctx = new PlayerContext(wav,buffer,bufferSize);
    (*andioPlayerBufferQueueItf)->RegisterCallback(andioPlayerBufferQueueItf,PlayerCallBack,ctx);

    //9.获取Play Interface通过对SetPlayState函数来启动播放音乐
    //一旦播放器被设置为播放状态,该音频播放器开始等待缓冲区排队就绪
    (*audioPlayerObject)->GetInterface(audioPlayerObject,SL_IID_PLAY,&audioPlayInterface);
    //设置播放状态
    (*audioPlayInterface)->SetPlayState(audioPlayInterface,SL_PLAYSTATE_PLAYING);

    //10.开始,让第一个缓冲区入队
    PlayerCallBack(andioPlayerBufferQueueItf,ctx);


    //关闭文件
    //CloseWaveFile(wav);
}

以上是关于Android音视频OpenSL ES音频播放示例一的主要内容,如果未能解决你的问题,请参考以下文章

Android音视频学习第7章:使用OpenSL ES进行音频解码

Android 音视频深入 十三 OpenSL ES 制作音乐播放器,能暂停和调整音量(附源码下载)

Android音视频十三OpenSL ES介绍&基于OpenSL ES实现音频采集

Android音视频十三OpenSL ES介绍&基于OpenSL ES实现音频采集

Android音视频十三OpenSL ES介绍&基于OpenSL ES实现音频采集

Android音视频十三OpenSL ES介绍&基于OpenSL ES实现音频采集