Android : 如何使用 OpenSL ES 更改音乐的播放速率

Posted

技术标签:

【中文标题】Android : 如何使用 OpenSL ES 更改音乐的播放速率【英文标题】:Android : How to change Playback Rate of music using OpenSL ES 【发布时间】:2012-06-21 02:28:51 【问题描述】:

我正在开发一个音乐播放器,我需要在不改变音高的情况下改变节奏(音乐的播放速度)。

我无法找到任何本机 android 类来执行此操作。我尝试了 SoundPool,但它不适用于大型音乐文件,而且它似乎也不适用于许多设备。我也尝试了 AudioTrack,但再次没有运气。

现在我正在尝试使用 OpenSL ES 处理音乐的 android NDK 音频示例。现在我只想在此示例中添加设置播放速率功能。

谁能告诉我如何在其中添加更改播放速率功能?

【问题讨论】:

【参考方案1】:

我已经解决了我的问题。如果有人需要,这是我完整的 OpenSL ES 原生代码:

#include <jni.h>

#include<android/log.h>
// LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog 넣어주세요
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, "OSLESMediaPlayer", __VA_ARGS__) 
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG  , "OSLESMediaPlayer", __VA_ARGS__) 
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO   , "OSLESMediaPlayer", __VA_ARGS__) 
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN   , "OSLESMediaPlayer", __VA_ARGS__) 
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR  , "OSLESMediaPlayer", __VA_ARGS__) 

// for native audio
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>

#include <assert.h>
#include <sys/types.h>

// engine interfaces
static SLObjectItf engineObject = NULL;
static SLEngineItf engineEngine;

// URI player interfaces
static SLObjectItf uriPlayerObject = NULL;
static SLPlayItf uriPlayerPlay;
static SLSeekItf uriPlayerSeek;
static SLPlaybackRateItf uriPlaybackRate;

// output mix interfaces
static SLObjectItf outputMixObject = NULL;

// playback rate (default 1x:1000)
static SLpermille playbackMinRate = 500;
static SLpermille playbackMaxRate = 2000;
static SLpermille playbackRateStepSize;

//Pitch
static SLPitchItf uriPlaybackPitch;
static SLpermille playbackMinPitch = 500;
static SLpermille playbackMaxPitch = 2000;

// create the engine and output mix objects
JNIEXPORT void Java_com_swssm_waveloop_audio_OSLESMediaPlayer_createEngine(
        JNIEnv* env, jclass clazz) 
    SLresult result;

    // create engine
    LOGD("create engine");
    result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
    assert(SL_RESULT_SUCCESS == result);

    // realize the engine
    LOGD("realize the engine");
    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    assert(SL_RESULT_SUCCESS == result);

    // get the engine interface, which is needed in order to create other objects
    LOGD("get the engine interface");
    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE,
            &engineEngine);
    assert(SL_RESULT_SUCCESS == result);

    // create output mix, with environmental reverb specified as a non-required interface
    LOGD("create output mix");
    const SLInterfaceID ids[1] = SL_IID_PLAYBACKRATE;
    const SLboolean req[1] = SL_BOOLEAN_FALSE;
    result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1,
            ids, req);
    assert(SL_RESULT_SUCCESS == result);

    // realize the output mix
    LOGD("realize the output mix");
    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    assert(SL_RESULT_SUCCESS == result);



JNIEXPORT void Java_com_swssm_waveloop_audio_OSLESMediaPlayer_releaseEngine(
        JNIEnv* env, jclass clazz) 
    // destroy URI audio player object, and invalidate all associated interfaces
    if (uriPlayerObject != NULL) 
        (*uriPlayerObject)->Destroy(uriPlayerObject);
        uriPlayerObject = NULL;
        uriPlayerPlay = NULL;
        uriPlayerSeek = NULL;
    

    // destroy output mix object, and invalidate all associated interfaces
    if (outputMixObject != NULL) 
        (*outputMixObject)->Destroy(outputMixObject);
        outputMixObject = NULL;
    

    // destroy engine object, and invalidate all associated interfaces
    if (engineObject != NULL) 
        (*engineObject)->Destroy(engineObject);
        engineObject = NULL;
        engineEngine = NULL;
    



/*
 void OnCompletion(JNIEnv* env, jclass clazz)
 
 jclass cls = env->GetObjectClass(thiz);
 if (cls != NULL)
 
 jmethodID mid = env->GetMethodID(cls, "OnCompletion", "()V");
 if (mid != NULL)
 
 env->CallVoidMethod(thiz, mid, 1234);
 
 
 */

void playStatusCallback(SLPlayItf play, void* context, SLuint32 event) 
    //LOGD("playStatusCallback");


// create URI audio player
JNIEXPORT jboolean Java_com_swssm_waveloop_audio_OSLESMediaPlayer_createAudioPlayer(
        JNIEnv* env, jclass clazz, jstring uri) 
    SLresult result;

    // convert Java string to UTF-8
    const jbyte *utf8 = (*env)->GetStringUTFChars(env, uri, NULL);
    assert(NULL != utf8);

    // configure audio source
    // (requires the INTERNET permission depending on the uri parameter)
    SLDataLocator_URI loc_uri =  SL_DATALOCATOR_URI, (SLchar *) utf8 ;
    SLDataFormat_MIME format_mime =  SL_DATAFORMAT_MIME, NULL,
            SL_CONTAINERTYPE_UNSPECIFIED ;
    SLDataSource audiosrc =  &loc_uri, &format_mime ;

    // configure audio sink
    SLDataLocator_OutputMix loc_outmix =  SL_DATALOCATOR_OUTPUTMIX,
            outputMixObject ;
    SLDataSink audioSnk =  &loc_outmix, NULL ;

    // create audio player
    const SLInterfaceID ids[2] =  SL_IID_SEEK, SL_IID_PLAYBACKRATE ;
    const SLboolean req[2] =  SL_BOOLEAN_FALSE, SL_BOOLEAN_TRUE ;
    result = (*engineEngine)->CreateAudioPlayer(engineEngine, &uriPlayerObject,
            &audioSrc, &audioSnk, 2, ids, req);
    // note that an invalid URI is not detected here, but during prepare/prefetch on Android,
    // or possibly during Realize on other platforms
    assert(SL_RESULT_SUCCESS == result);

    // release the Java string and UTF-8
    (*env)->ReleaseStringUTFChars(env, uri, utf8);

    // realize the player
    result = (*uriPlayerObject)->Realize(uriPlayerObject, SL_BOOLEAN_FALSE);
    // this will always succeed on Android, but we check result for portability to other platforms
    if (SL_RESULT_SUCCESS != result) 
        (*uriPlayerObject)->Destroy(uriPlayerObject);
        uriPlayerObject = NULL;
        return JNI_FALSE;
    

    // get the play interface
    result = (*uriPlayerObject)->GetInterface(uriPlayerObject, SL_IID_PLAY,
            &uriPlayerPlay);
    assert(SL_RESULT_SUCCESS == result);

    // get the seek interface
    result = (*uriPlayerObject)->GetInterface(uriPlayerObject, SL_IID_SEEK,
            &uriPlayerSeek);
    assert(SL_RESULT_SUCCESS == result);

    // get playback rate interface
    result = (*uriPlayerObject)->GetInterface(uriPlayerObject,
            SL_IID_PLAYBACKRATE, &uriPlaybackRate);
    assert(SL_RESULT_SUCCESS == result);

    /*  // get playback pitch interface
     result = (*uriPlayerObject)->GetInterface(uriPlayerObject, SL_IID_PITCH, &uriPlaybackPitch);
     assert(SL_RESULT_SUCCESS == result);*/

    // register callback function
    result = (*uriPlayerPlay)->RegisterCallback(uriPlayerPlay,
            playStatusCallback, 0);
    assert(SL_RESULT_SUCCESS == result);
    result = (*uriPlayerPlay)->SetCallbackEventsMask(uriPlayerPlay,
            SL_PLAYEVENT_HEADATEND); // head at end
    assert(SL_RESULT_SUCCESS == result);

    SLmillisecond msec;
    result = (*uriPlayerPlay)->GetDuration(uriPlayerPlay, &msec);
    assert(SL_RESULT_SUCCESS == result);

    // no loop
    result = (*uriPlayerSeek)->SetLoop(uriPlayerSeek, SL_BOOLEAN_TRUE, 0, msec);
    assert(SL_RESULT_SUCCESS == result);


    SLuint32 capa;
        result = (*uriPlaybackRate)->GetRateRange(uriPlaybackRate, 0,
                &playbackMinRate, &playbackMaxRate, &playbackRateStepSize, &capa);
        assert(SL_RESULT_SUCCESS == result);

        result = (*uriPlaybackRate)->SetPropertyConstraints(uriPlaybackRate,
                        SL_RATEPROP_PITCHCORAUDIO);

                    if (SL_RESULT_PARAMETER_INVALID == result) 
                        LOGD("Parameter Invalid");
                    
                    if (SL_RESULT_FEATURE_UNSUPPORTED == result) 
                            LOGD("Feature Unsupported");
                        
                    if (SL_RESULT_SUCCESS == result) 
                        assert(SL_RESULT_SUCCESS == result);
                            LOGD("Success");
                        
    /*
     result = (*uriPlaybackPitch)->GetPitchCapabilities(uriPlaybackPitch, &playbackMinPitch, &playbackMaxPitch);
     assert(SL_RESULT_SUCCESS == result);*/

    /*
     SLpermille minRate, maxRate, stepSize, rate = 1000;
     SLuint32 capa;
     (*uriPlaybackRate)->GetRateRange(uriPlaybackRate, 0, &minRate, &maxRate, &stepSize, &capa);

     (*uriPlaybackRate)->SetRate(uriPlaybackRate, minRate);
     */
    return JNI_TRUE;


JNIEXPORT void Java_com_swssm_waveloop_audio_OSLESMediaPlayer_releaseAudioPlayer(
        JNIEnv* env, jclass clazz) 
    // destroy URI audio player object, and invalidate all associated interfaces
    if (uriPlayerObject != NULL) 
        (*uriPlayerObject)->Destroy(uriPlayerObject);
        uriPlayerObject = NULL;
        uriPlayerPlay = NULL;
        uriPlayerSeek = NULL;
        uriPlaybackRate = NULL;
    



void setPlayState(SLuint32 state) 
    SLresult result;

    // make sure the URI audio player was created
    if (NULL != uriPlayerPlay) 

        // set the player's state
        result = (*uriPlayerPlay)->SetPlayState(uriPlayerPlay, state);
        assert(SL_RESULT_SUCCESS == result);
    



SLuint32 getPlayState() 
    SLresult result;

    // make sure the URI audio player was created
    if (NULL != uriPlayerPlay) 

        SLuint32 state;
        result = (*uriPlayerPlay)->GetPlayState(uriPlayerPlay, &state);
        assert(SL_RESULT_SUCCESS == result);

        return state;
    

    return 0;



// play
JNIEXPORT void Java_com_swssm_waveloop_audio_OSLESMediaPlayer_play(JNIEnv* env,
        jclass clazz) 
    setPlayState(SL_PLAYSTATE_PLAYING);


// stop
JNIEXPORT void Java_com_swssm_waveloop_audio_OSLESMediaPlayer_stop(JNIEnv* env,
        jclass clazz) 
    setPlayState(SL_PLAYSTATE_STOPPED);


// pause
JNIEXPORT void Java_com_swssm_waveloop_audio_OSLESMediaPlayer_pause(JNIEnv* env,
        jclass clazz) 
    setPlayState(SL_PLAYSTATE_PAUSED);


// pause
JNIEXPORT jboolean Java_com_swssm_waveloop_audio_OSLESMediaPlayer_isPlaying(
        JNIEnv* env, jclass clazz) 
    return (getPlayState() == SL_PLAYSTATE_PLAYING);


// set position
JNIEXPORT void Java_com_swssm_waveloop_audio_OSLESMediaPlayer_seekTo(
        JNIEnv* env, jclass clazz, jint position) 
    if (NULL != uriPlayerPlay) 

        //SLuint32 state = getPlayState();
        //setPlayState(SL_PLAYSTATE_PAUSED);

        SLresult result;

        result = (*uriPlayerSeek)->SetPosition(uriPlayerSeek, position,
                SL_SEEKMODE_FAST);
        assert(SL_RESULT_SUCCESS == result);

        //setPlayState(state);
    



// get duration
JNIEXPORT jint Java_com_swssm_waveloop_audio_OSLESMediaPlayer_getDuration(
        JNIEnv* env, jclass clazz) 
    if (NULL != uriPlayerPlay) 

        SLresult result;

        SLmillisecond msec;
        result = (*uriPlayerPlay)->GetDuration(uriPlayerPlay, &msec);
        assert(SL_RESULT_SUCCESS == result);

        return msec;
    

    return 0.0f;


// get current position
JNIEXPORT jint Java_com_swssm_waveloop_audio_OSLESMediaPlayer_getPosition(
        JNIEnv* env, jclass clazz) 
    if (NULL != uriPlayerPlay) 

        SLresult result;

        SLmillisecond msec;
        result = (*uriPlayerPlay)->GetPosition(uriPlayerPlay, &msec);
        assert(SL_RESULT_SUCCESS == result);

        return msec;
    

    return 0.0f;


//llllllllllllllllllll

JNIEXPORT void Java_com_swssm_waveloop_audio_OSLESMediaPlayer_setPitch(
        JNIEnv* env, jclass clazz, jint rate) 
    if (NULL != uriPlaybackPitch) 
        SLresult result;

        result = (*uriPlaybackPitch)->SetPitch(uriPlaybackPitch, rate);
        assert(SL_RESULT_SUCCESS == result);
    


JNIEXPORT void Java_com_swssm_waveloop_audio_OSLESMediaPlayer_setRate(
        JNIEnv* env, jclass clazz, jint rate) 
    if (NULL != uriPlaybackRate) 
        SLresult result;

        result = (*uriPlaybackRate)->SetRate(uriPlaybackRate, rate);
            assert(SL_RESULT_SUCCESS == result);


    


JNIEXPORT jint Java_com_swssm_waveloop_audio_OSLESMediaPlayer_getRate(
        JNIEnv* env, jclass clazz) 
    if (NULL != uriPlaybackRate) 
        SLresult result;

        SLpermille rate;
        result = (*uriPlaybackRate)->GetRate(uriPlaybackRate, &rate);
        assert(SL_RESULT_SUCCESS == result);

        return rate;
    

    return 0;


// create URI audio player
JNIEXPORT jboolean Java_com_swssm_waveloop_audio_OSLESMediaPlayer_setLoop(
        JNIEnv* env, jclass clazz, jint startPos, jint endPos) 
    SLresult result;

    result = (*uriPlayerSeek)->SetLoop(uriPlayerSeek, SL_BOOLEAN_TRUE, startPos,
            endPos);
    assert(SL_RESULT_SUCCESS == result);

    return JNI_TRUE;


// create URI audio player
JNIEXPORT jboolean Java_com_swssm_waveloop_audio_OSLESMediaPlayer_setNoLoop(
        JNIEnv* env, jclass clazz) 
    SLresult result;
    if (NULL != uriPlayerSeek) 
        // enable whole file looping
        result = (*uriPlayerSeek)->SetLoop(uriPlayerSeek, SL_BOOLEAN_TRUE, 0,
                SL_TIME_UNKNOWN);
        assert(SL_RESULT_SUCCESS == result);

    
    return JNI_TRUE;

只需使用ndk-build 命令编译并使用它。如果有人成功改变音调,请告诉我解决方案。

这是 android.mk 文件

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := audio-tools

LOCAL_SRC_FILES := OSLESMediaPlayer.c


LOCAL_CFLAGS := -DHAVE_CONFIG_H -DFPM_ARM -ffast-math -O3

LOCAL_LDLIBS    += -lOpenSLES -llog

include $(BUILD_SHARED_LIBRARY)

和 Application.mk 文件

APP_STL := gnustl_static
APP_CPPFLAGS += -fexceptions -frtti
APP_ABI := armeabi armeabi-v7a

还有包装类,你可以直接在你的项目中使用它的功能

package com.swssm.waveloop.audio;
public class OSLESMediaPlayer 
    public native void createEngine();
    public native void releaseEngine();
    public native boolean createAudioPlayer(String uri);
    public native void releaseAudioPlayer();
    public native void play();
    public native void stop();
    public native void pause();
    public native boolean isPlaying();

    public native void seekTo(int position);
    public native int getDuration();
    public native int getPosition();

    public native void setPitch(int rate);

    public native void setRate(int rate);
    public native int getRate();

    public native void setLoop( int startPos, int endPos );
    public native void setNoLoop();


    public interface OnCompletionListener 
        public void OnCompletion();
    

    private OnCompletionListener mCompletionListener;
    public void SetOnCompletionListener( OnCompletionListener listener )
    
        mCompletionListener = listener;
    


    private void OnCompletion()
    
        mCompletionListener.OnCompletion();

        int position = getPosition();
        int duration = getDuration();
        if( position != duration )
        
            int a = 0;

        
        else
        
            int c = 0;

        
    

【讨论】:

感谢分享您的知识。你能告诉我它是否适用于 .Mp3 文件。我可以使用这个库和代码来改变 .mp3 文件的速度吗?高级 vipul 中的谢谢 是的。它适用于所有类型的 MP3、WAV 等文件格式。但不幸的是,节奏改变功能不适用于 ICS。我不知道为什么,但这个功能已从 Android ICS 中的 OpenSL ES 中删除。 谢谢。在您的代码 sn-p 中,您包含 4 个文件(#include #include #include #include )我在哪里找到这些文件。 没有办法。您必须使用 API 级别 9,因为 API 级别 9 中引入了 OpenSL ES。如果您不想使用 OpenSL ES,请使用 FFMPEG lib。但是使用 OpenSL ES 要复杂得多 您好,首先使用createEngine()方法创建引擎。然后使用 createAudioPlayer(FILE PATH);打开文件,然后你可以使用 setPitch(int rate);或 setRate(int rate);函数与 play();.【参考方案2】:

这可能会有所帮助(取自 NDK OpenSL 文档):

播放率

支持的播放速率范围和功能可能会有所不同 取决于平台版本和实现,因此应该是 在运行时通过查询 PlaybackRate::GetRateRange 或 PlaybackRate::GetCapabilitiesOfRate。

也就是说,一些关于 典型的速率范围可能有用:在 Android 2.3 中,单次播放 费率范围从 500 per mille 到 2000 per mille 包括在内 支持,具有属性 SL_RATEPROP_NOPITCHCORAUDIO。在安卓 4.0 中 PCM 中的数据源通常支持相同的速率范围 格式,以及其他格式的统一速率范围。

【讨论】:

以上是关于Android : 如何使用 OpenSL ES 更改音乐的播放速率的主要内容,如果未能解决你的问题,请参考以下文章

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

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

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

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

Android OpenSL ES 开发:Android OpenSL 介绍和开发流程说明

使用 OpenSL ES Android 同时播放多个音效