23.Eclipse下Ndk开发(OpenSL ES播放音频wav)

OpenSL ES(Open Sound Library for Embedded Systems)

网上解释OpenSL ES是跨平台、针对嵌入式系统精心优化的硬件音频加速API。它为嵌入式移动多媒体设备上的本地应用程序开发者提供标准化, 高性能,低响应时间的音频功能实现方法,并实现软/硬件音频性能的直接跨平台部署,降低执行难度,促进高级音频市场的发展。

NDK开发OpenSL ES跨平台高效音频解决方案.png

接下来直接上代码,注释都在代码中,具体的开发步骤可以参考
03.Eclipse下Ndk开发(以文件加密为例模拟一下开发过程)

AudioPlayer.java

package com.example.openslaudio;

public class AudioPlayer {

    public native static void play(String filePath);
    
    static{
        System.loadLibrary("OpenSLAudioPlayer");
    }
}

MainActivity.java

package com.example.openslaudio;

import java.io.File;

import android.Manifest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;

public class MainActivity extends Activity {

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

    public void play(View btn){
        
        new Thread(){
            public void run() {
                File file = new File(Environment.getExternalStorageDirectory(),"text.wav");
                AudioPlayer.play(file.getAbsolutePath());
            }
        }.start();
    }
}

通过环境变量的方式引入transcode-1.1.7/avilib需要做一些配置,详见下图,在C/C++ Build中设置NDK_MODULE_PATH(必须是这个名字),这个路径下放置我们要使用的共享库transcode-1.1.7,至于为什么要这样设置,很简单,如果一个库,你好几个项目都要使用,那么没有必要在每个项目中都添加库文件,只要设置环境变量,然后引入即可,NDKROOT设置ndk路径也是同样道理


共享库环境变量设置.png

Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := OpenSLAudioPlayer
LOCAL_SRC_FILES := OpenSLAudioPlayer.cpp

# Use WAVLib static library
LOCAL_STATIC_LIBRARIES += wavlib_static

# Link with OpenSL ES
LOCAL_LDLIBS += -lOpenSLES -llog

include $(BUILD_SHARED_LIBRARY)

# Import WAVLib library module,如果不引入的话,wavlib.h是无法使用的,主要作用
#就是解码,然后交给OpenSE ES去播放
$(call import-module,transcode-1.1.7/avilib)

com_example_openslaudio_AudioPlayer.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_openslaudio_AudioPlayer */

#ifndef _Included_com_example_openslaudio_AudioPlayer
#define _Included_com_example_openslaudio_AudioPlayer
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_openslaudio_AudioPlayer
 * Method:    play
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_com_example_openslaudio_AudioPlayer_play
  (JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

OpenSLAudioPlayer.cpp

#include <jni.h>
#include "com_example_openslaudio_AudioPlayer.h"
#include "CreateBufferQueueAudioPlayer.cpp"
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <android/log.h>
extern "C" {
#include "wavlib.h"
}
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"renzhenming",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"renzhenming",FORMAT,##__VA_ARGS__);

#define ARRAR_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;


//打开文件
WAV OpenWaveFile(JNIEnv *env,jstring jinput){
    const char* cinput_file = env->GetStringUTFChars(jinput,NULL);

    /*typedef enum {
        WAV_SUCCESS,         no error so far
        WAV_NO_MEM,          can't acquire requested amount of memory
        WAV_IO_ERROR,        unable to read/write (often write) data
        WAV_BAD_FORMAT,      acquired data doesn't seem wav-compliant
        WAV_BAD_PARAM,       bad parameter for requested operation
        WAV_UNSUPPORTED,     feature not yet supported by wavlib
    } WAVError;*/
    WAVError err;

    //WAV wav_open(const char *filename, WAVMode mode, WAVError *err);
    /**
     * typedef enum {
     *      WAV_READ  = 1,  open WAV file in read-only mode
     *      WAV_WRITE = 2,  open WAV file in write-only mode
     *      WAV_PIPE  = 4,  cut SEEEKs
     *      } WAVMode;
     */

    WAV wav = wav_open(cinput_file,WAV_READ,&err);

    LOGI("%d",wav_get_bitrate(wav));
    env->ReleaseStringUTFChars(jinput,cinput_file);
    if(wav == 0){
        //get a human-readable short description of an error code
        //Return Value:a pointer to a C-string describing the given error code,
        //or NULL if given error code isn't known.
        LOGE("%s",wav_strerror(err));
    }
    return wav;
}

//实例化对象
void RealizeObject(SLObjectItf object){
    //非异步(会阻塞线程)
    /**
     *  SLresult (*Realize) (
     *      SLObjectItf self,
     *      SLboolean async
     *  );
     */
    (*object)->Realize(object,SL_BOOLEAN_FALSE);
}

//上下文
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 CloseWaveFile(WAV wav){
    wav_close(wav);
}


//回调函数
/*
 * wav_read_data:
 *      read a buffer of pcm data from given wav file. Delivers data
 *      in wav-native-byte-order (little endian).
 *      This function doesn't mess with given data, it just reads
 *      data verbatim from wav file. so caller must take care to
 *      split/join channel data or do any needed operation.
 *
 * Parameters:
 *      handle: wav handle to write data in
 *      buffer: pointer to data to store the data readed
 *      bufsize: size of given buffer.
 * Return Value:
 *      return of bytes effectively readed from wav file.
 *      -1 means an error.
 * Side effects:
 *      N/A
 * Preconditions:
 *      given wav handle is a valid one obtained as return value of
 *      wav_open/wav_fdopen; wav handle was opened in WAV_READ mode.
 *      bufsize is a multiple of 2.
 * Postconditions:
 *      N/A
 *
 * ssize_t wav_read_data(WAV handle, uint8_t *buffer, size_t bufsize);
 */
void playerCallBack(SLAndroidSimpleBufferQueueItf andioPlayerBufferQueue,void *context){
    PlayerContext *ctx = (PlayerContext*)context;
    //读取数据
    ssize_t readSize = wav_read_data(ctx->wav,ctx->buffer,ctx->bufferSize);
    if(readSize > 0){
        /**
         *  SLresult (*Enqueue) (
                SLAndroidSimpleBufferQueueItf self,
                const void *pBuffer,
                SLuint32 size
            );
         */
        (*andioPlayerBufferQueue)->Enqueue(andioPlayerBufferQueue,ctx->buffer,readSize);
    }else{
        //destory context中保存的东西

        //关闭文件
        CloseWaveFile(ctx->wav);

        //释放缓存
        delete ctx->buffer;
    }
}


JNIEXPORT void JNICALL Java_com_example_openslaudio_AudioPlayer_play
  (JNIEnv *env, jclass jclazz, jstring input){

    //打开文件
    WAV wav = OpenWaveFile(env,input);

    //创建OpenSL ES引擎,OpenSL ES在Android平台下默认是线程安全的,这样设置是为了兼容其它平台
    /**
     *  SL_API SLresult SLAPIENTRY slCreateEngine(
            SLObjectItf             *pEngine,
            SLuint32                numOptions,
            const SLEngineOption    *pEngineOptions,
            SLuint32                numInterfaces,
            const SLInterfaceID     *pInterfaceIds,
            const SLboolean         * pInterfaceRequired
        );
     */
    SLEngineOption engineOptions[] ={
            {(SLuint32)SL_ENGINEOPTION_THREADSAFE,(SLuint32)SL_BOOLEAN_TRUE}
    };
    slCreateEngine(&engineObject,ARRAR_LEN(engineObject),engineOptions,0,0,0);//没有接口
    if(engineObject == NULL){
        LOGI("%s","engineObject == NULL");
    }else{
        LOGI("%s","engineObject != NULL");
    }
    //实例化对象,对象创建之后,处于未实例化状态,对象虽然存在但未分配任何资源,使用前先实例化(使用完之后destroy)
    RealizeObject(engineObject);

    //获取引擎接口
    /**
     *  SLresult (*GetInterface) (
            SLObjectItf self,
            const SLInterfaceID iid,
            void * pInterface
        );

        typedef const struct SLInterfaceID_ {
            SLuint32 time_low;
            SLuint16 time_mid;
            SLuint16 time_hi_and_version;
            SLuint16 clock_seq;
            SLuint8  node[6];
        } * SLInterfaceID;
        extern SL_API const SLInterfaceID SL_IID_ENGINE;
     */
    (*engineObject)->GetInterface(engineObject,SL_IID_ENGINE,&engineInterface);

    //创建输出混音器
    /**
     *  SLresult (*CreateOutputMix) (
            SLEngineItf self,
            SLObjectItf * pMix,
            SLuint32 numInterfaces,
            const SLInterfaceID * pInterfaceIds,
            const SLboolean * pInterfaceRequired
        );
     */
    (*engineInterface)->CreateOutputMix(engineInterface,&outputMixObject,0,0,0);//没有接口

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

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

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

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

    //获取缓冲区队列接口Buffer Queue Interface
    //通过缓冲区接口对缓冲区进行排序播放
    /**
     *  SLresult (*GetInterface) (
            SLObjectItf self,
            const SLInterfaceID iid,
            void * pInterface
        );

        typedef const struct SLInterfaceID_ {
            SLuint32 time_low;
            SLuint16 time_mid;
            SLuint16 time_hi_and_version;
            SLuint16 clock_seq;
            SLuint8  node[6];
        } * SLInterfaceID;
        extern SL_API const SLInterfaceID SL_IID_BUFFERQUEUE;
     */
    (*audioPlayerObject)->GetInterface(audioPlayerObject,SL_IID_BUFFERQUEUE,&andioPlayerBufferQueueItf);

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

    //获取Play Interface 通过对SetPlayState函数来启动播放音乐
    //一旦播放器被置为播放状态。该音频播放器开始等待缓冲区排队就绪
    /**
     *  SLresult (*GetInterface) (
            SLObjectItf self,
            const SLInterfaceID iid,
            void * pInterface
        );
     */
    (*audioPlayerObject)->GetInterface(audioPlayerObject,SL_IID_PLAY,&audioPlayInterface);

    //设置播放状态
    /**
     *  SLresult (*SetPlayState) (
            SLPlayItf self,
            SLuint32 state
        );
     */
    (*audioPlayInterface)->SetPlayState(audioPlayInterface,SL_PLAYSTATE_PLAYING);

    //开始,让第一个缓冲区入队
    playerCallBack(andioPlayerBufferQueueItf,ctx);
}

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);
}

权限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,448评论 25 707
  • 前段时间由于做比赛的事,一直都没时间写博客,现在终于可以补上一篇了,一直想学习一点NDK开发的知识,但是迟迟没有动...
    冰鉴IT阅读 1,743评论 7 18
  • 一、NDK产生的背景 Android平台从诞生起,就已经支持C、C++开发。众所周知,Android的SDK基于J...
    Ten_Minutes阅读 3,479评论 1 27
  • 今天早上不知道为何如此心烦,动手打了一巴掌女儿,想到当时的速度真的非常快,感觉自己身体里面的魔鬼出来了,就那样...
    墨墨爱在行动阅读 671评论 0 0
  • 记得前几年听过一句话说我抽烟喝酒纹身骂人,但是我是个好姑娘。 当时不懂,如今碰到了这样的疑问还是不懂。如果这样是好...
    会飞的猪猪阅读 204评论 0 1