参考:
- http://pielot.org/2010/12/14/openal-on-android/
- http://blog.livedoor.jp/itahidamito/archives/51671451.html

OpenAL (Open Audio Library)は、マルチチャンネル3次元定位オーディオを効率よく表現するように設計されたクロスプラットフォームのオーディオAPIです。

ここでは、Android上でこのOpenALを使う方法を説明します。

* はじめる前に [#r9358f84]
* はじめる前に [#m6d6a31a]

ここで紹介する方法は、基本的には上記の参考URLで紹介されていた内容に、若干の修正と補足を加えたものです。

参考までに、私が作成したプロジェクトをこちら(http://akihiro-i.net/~akihiro-i/public_files/20110807_MyHelloOpenAL.zip)に置いておきます。

なお、Androidの開発環境とNDKは既に準備されているものとします。これらについては、以下のページを見てください。

- [[Windows7(64bit)でAndroidの開発環境を整える:http://akihiro-i.net/~akihiro-i/wiki/index.php?Windows7%A1%CA64bit%A1%CB%A4%C7Android%A4%CE%B3%AB%C8%AF%B4%C4%B6%AD%A4%F2%C0%B0%A4%A8%A4%EB]]
- [[Windows7(64bit)でAndroid NDKを使ってみる:http://akihiro-i.net/~akihiro-i/wiki/index.php?Windows7%A1%CA64bit%A1%CB%A4%C7Android%20NDK%A4%F2%BB%C8%A4%C3%A4%C6%A4%DF%A4%EB%A1%CAC%2FC%2B%2B%A4%C7Android%B3%AB%C8%AF%A1%CB]]
- [[Ubuntu上でAndroidの開発環境を整える:http://akihiro-i.net/~akihiro-i/wiki/index.php?Ubuntu%BE%E5%A4%C7Android%A4%CE%B3%AB%C8%AF%B4%C4%B6%AD%A4%F2%C0%B0%A4%A8%A4%EB%A1%CAAndroid%20SDK%A4%CE%A5%A4%A5%F3%A5%B9%A5%C8%A1%BC%A5%EB%A4%AB%A4%E9eclipse%A4%C7%A4%CEAVD%A4%CE%B5%AF%C6%B0%A4%DE%A4%C7%A1%CB]]
- [[Ubuntu11.04でAndroid NDKを使ってみる:http://akihiro-i.net/~akihiro-i/wiki/index.php?Ubuntu11.04%A4%C7Android%20NDK%A4%F2%BB%C8%A4%C3%A4%C6%A4%DF%A4%EB%A1%CAC%2FC%2B%2B%A4%C7Android%B3%AB%C8%AF%A1%CB]]

* 「MyHelloOpenAL」プロジェクトを作成する [#meddf237]
説明はWindows上でcygwinを使った場合で書いていますが、Ubuntu等のLinuxからでも同様の方法で利用できると思います。

* 「MyHelloOpenAL」プロジェクトを作成する [#p3e6c1d1]

eclipseの新規作成から、Androidプロジェクトを作成してください。特別な設定は不要です。

ただし、ndk-buildでエラーが出るため、プロジェクトはPATHに空白が含まれない場所に作成する必要があります。


* Android用に修正されたOpenALのソースコードをダウンロード [#i7b19edc]
* Android用に修正されたOpenALのソースコードをダウンロード [#p23c6aab]

こちら( http://repo.or.cz/w/openal-soft/android.git )から最新版のソースをDLします。

以下の説明ではこのバージョン(http://repo.or.cz/w/openal-soft/android.git/snapshot/ea44b95252ce15dd38fb7563477e9e35b1c147dc.tar.gz)のソースを使用します。

ダウンロードしたファイルを解凍し、先ほど作成した「HelloOpenAL」プロジェクトにコピーします。

* OpenALをコンパイルための準備 [#l6c497cb]
* OpenALをコンパイルための準備 [#ee30cf0e]

コピーしたフォルダ名を「android」から「openal」へ変更してください。
さらに、サブフォルダの「android」も「openal」へ変更します。

 <PROJECT_HOME>/android/android
 ⇒ <PROJECT_HOME>/openal/openal


次にconfig.hをincludeへコピーします。

 <PROJECT_HOME>/openal/openal/jni/config.h

から、

 <PROJECT_HOME>/openal/include/

へコピーしてください。

* Android.mkの作成 [#aa977d8f]
* Android.mkの作成 [#id16d321]

まず<PROJECT_HOME>へjniフォルダを作成します。

  <PROJECT_HOME>/jni

その中に、下記の内容でAndroid.mk(makefileのようなもの)を作成します。


 TARGET_PLATFORM := android-3
 ROOT_PATH := $(call my-dir)
 
 
 ##################################################################################
 include $(CLEAR_VARS)
 
 LOCAL_MODULE     := openal
 LOCAL_ARM_MODE   := arm
 LOCAL_PATH       := $(ROOT_PATH)
 LOCAL_C_INCLUDES := $(LOCAL_PATH) $(LOCAL_PATH)/../openal/include $(LOCAL_PATH)/../openal/OpenAL32/Include
 LOCAL_SRC_FILES  := ../openal/OpenAL32/alAuxEffectSlot.c \
                    ../openal/OpenAL32/alBuffer.c        \
                    ../openal/OpenAL32/alDatabuffer.c    \
                    ../openal/OpenAL32/alEffect.c        \
                    ../openal/OpenAL32/alError.c         \
                    ../openal/OpenAL32/alExtension.c     \
                    ../openal/OpenAL32/alFilter.c        \
                    ../openal/OpenAL32/alListener.c      \
                    ../openal/OpenAL32/alSource.c        \
                    ../openal/OpenAL32/alState.c         \
                    ../openal/OpenAL32/alThunk.c         \
                    ../openal/Alc/ALc.c                  \
                    ../openal/Alc/alcConfig.c            \
                    ../openal/Alc/alcEcho.c              \
                    ../openal/Alc/alcModulator.c         \
                    ../openal/Alc/alcReverb.c            \
                    ../openal/Alc/alcRing.c              \
                    ../openal/Alc/alcThread.c            \
                    ../openal/Alc/ALu.c                  \
                    ../openal/Alc/android.c              \
                    ../openal/Alc/bs2b.c                 \
                    ../openal/Alc/null.c                 \
 
 LOCAL_CFLAGS     := -DAL_BUILD_LIBRARY -DAL_ALEXT_PROTOTYPES
 LOCAL_LDLIBS     := -llog -Wl,-s
 
 include $(BUILD_SHARED_LIBRARY)
 
 ##################################################################################


*OpenALのコンパイル [#l98abb52]
*OpenALのコンパイル [#hb107263]

cygwinを起動し、プロジェクトのディレクトリへ移動

 $ cd /cygdrive/c/eclipse/workspace/MyHelloOpenAL

ndk-buildにより、Android.mkの記述に従ってコンパイルを開始。

 $ ndk-build

※nkd-buildが実行できない場合は、NDKがインストールされていないか、パスが取っていない可能性があります。

コンパイルが成功したら、「<PROJECT_HOME>/libs/armeabi」に「libopenal.so」が生成されます。

* アクティビティにネイティブインタフェースを定義 [#pfe7f0f4]
* アクティビティにネイティブインタフェースを定義 [#s357cff3]

MyHelloOpenAL Activityに、ネイティブメソッド「play」を定義します。

プロジェクトを以下のように修正してください。

※ package は自分の環境に合わせて書き直すこと。

※ play()で呼び出すwavファイルのパスと名前も、実際に使用するAndoridの環境にあわせて修正してください。

 package net.akihiroi.MyHelloOpenAL;
 
 import android.app.Activity;
 import android.os.Bundle;
 import android.util.Log;
 
 public class MyHelloOpenAL extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        System.loadLibrary("openal");
        System.loadLibrary("openaltest");
        int ret = play("/mnt/sdcard/external_sd/wav/test.wav");
        Log.i("HelloOpenAL", ""+ret);
    }
 
    private native int play(String filename);
 }


* ネイティブメソッドの作成 [#b783d1f7]
* ネイティブメソッドの作成 [#za19c306]

まず、<PROJECT_HOME>に移動し、

 $ javah.exe -classpath bin -d jni net.akihiroi.MyHelloOpenAL.MyHelloOpenAL

として、<PROJECT_HOME>/jniに「net_akihiroi_MyHelloOpenAL_MyHelloOpenAL.h」というヘッダファイルを作成します。

続いて、<PROJECT_HOME>/jniフォルダに、ネイティブメソッドの元となるCのコードを作成します。

WindowsやLinux等では、ALUT(OpenAL Utility Toolkit)をしようすることで簡単にwavファイル等が扱えるのですが、現状では、ALUTをAndroidで動かすことは困難なため、wavファイルを読み込んでバッファに入れる関数も作成します。

ファイル名は「net_akihiroi_MyHelloOpenAL_MyHelloOpenAL.c」とし、以下の内容を記述します。

ここで、C/C++ の 「Java_net_akihiroi_MyHelloOpenAL_MyHelloOpenAL_play()」関数が、Javaの「net.akihiroi.MyHelloOpenAL.MyHelloOpenAL.play()」ネイティブメソッドと対応します。 

プロジェクトのパッケージ名にあわせて関数名を修正してください。

 #include "net_akihiroi_MyHelloOpenAL_MyHelloOpenAL.h"
 
 #include <stdio.h>
 #include <stddef.h>
 #include <string.h>
 #include <AL/al.h>
 #include <AL/alc.h>
 
 
 typedef struct {
   char  riff[4];//'RIFF'
   unsigned int riffSize;
   char  wave[4];//'WAVE'
   char  fmt[4];//'fmt '
   unsigned int fmtSize;
   unsigned short format;
   unsigned short channels;
   unsigned int samplesPerSec;
   unsigned int bytesPerSec;
   unsigned short blockAlign;
   unsigned short bitsPerSample;
   char  data[4];//'data'
   unsigned int dataSize;
 }BasicWAVEHeader;
 
 
 //WARNING: This Doesn't Check To See If These Pointers Are Valid
 char* readWAV(char* filename,BasicWAVEHeader* header){
    char* buffer = 0;
    FILE* file = fopen(filename,"rb");
    if (!file) {
        return 0;
    }
 
    if (fread(header,sizeof(BasicWAVEHeader),1,file)){
        if (!(//these things *must* be valid with this basic header
            memcmp("RIFF",header->riff,4) ||
            memcmp("WAVE",header->wave,4) ||
            memcmp("fmt ",header->fmt,4)  ||
            memcmp("data",header->data,4)
        )){
        
            buffer = (char*)malloc(header->dataSize);
            if (buffer){
                if (fread(buffer,header->dataSize,1,file)){
                    fclose(file);
                    return buffer;
                } 
                free(buffer);
            } 
        }
    }
    fclose(file);
    return 0;
 }
 
 ALuint createBufferFromWave(char* data,BasicWAVEHeader header){
 
    ALuint buffer = 0;
    ALuint format = 0;
    switch (header.bitsPerSample){
    case 8:
        format = (header.channels == 1) ? AL_FORMAT_MONO8 : AL_FORMAT_STEREO8;
        break;
    case 16:
        format = (header.channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
        break;
    default:
        return 0;
    }
 
    alGenBuffers(1,&buffer);
    alBufferData(buffer,format,data,header.dataSize,header.samplesPerSec);
    return buffer;
 }
 
 
 JNIEXPORT jint JNICALL Java_net_akihiroi_MyHelloOpenAL_MyHelloOpenAL_play
  (JNIEnv * env, jobject obj, jstring filename) {
 
        // Global Variables
        ALCdevice* device = 0;
        ALCcontext* context = 0;
        const ALint context_attribs[] = { ALC_FREQUENCY, 22050, 0 };
        
        // Initialization
        device = alcOpenDevice(0);        
        context = alcCreateContext(device, context_attribs);    
        alcMakeContextCurrent(context);
        
        // Create audio buffer
        ALuint buffer;
        const char* fnameptr = (*env)->GetStringUTFChars(env, filename, NULL);        
        BasicWAVEHeader header;
        char* data = readWAV(fnameptr,&header);
        if (data){
            //Now We've Got A Wave In Memory, Time To Turn It Into A Usable Buffer
            buffer = createBufferFromWave(data,header);
	        if (!buffer){
                free(data);
                return -1;
            }
        
        } else {
            return -1;
        }
        
        // Create source from buffer and play it
        ALuint source = 0;
        alGenSources(1, &source );
        alSourcei(source, AL_BUFFER, buffer);
        
        // Play source
        alSourcePlay(source);
        
        int        sourceState = AL_PLAYING;
        do {
            alGetSourcei(source, AL_SOURCE_STATE, &sourceState);
        } while(sourceState == AL_PLAYING);
        
        // Release source
        alDeleteSources(1, &source);
        
                
        // Release audio buffer
        alDeleteBuffers(1, &buffer);
            
        // Cleaning up
        alcMakeContextCurrent(0);
         alcDestroyContext(context);
         alcCloseDevice(device);
         
         return 0;
 }


* ネイティブメソッドをコンパイル [#n1ecf7ec]
* ネイティブメソッドをコンパイル [#la403673]

下記の記述をAndroid.mkに追記してください。

 ###############################################################################
 
 include $(CLEAR_VARS)
 
 LOCAL_MODULE     := openaltest
 LOCAL_ARM_MODE   := arm
 LOCAL_PATH       := $(ROOT_PATH)
 LOCAL_C_INCLUDES := $(LOCAL_PATH)/../openal/include
 LOCAL_SRC_FILES  := net_akihiroi_MyHelloOpenAL_MyHelloOpenAL.c 	\
                     
 LOCAL_LDLIBS     := -llog -Wl,-s
 
 LOCAL_SHARED_LIBRARIES := libopenal
 
 include $(BUILD_SHARED_LIBRARY)
 
 ###############################################################################

書き直したら、再度コンパイルしてください。

 $ ndk-build

エラーがなければ、libopenaltest.soが作成されるはずです。

*実行前の準備 [#s0353757]
*実行前の準備 [#i67dd5fb]

デバイスのSD Cardに、OpenALで再生するための.wavファイルを準備します。

上記のプロジェクトでは

        int ret = play("/mnt/sdcard/external_sd/wav/test.wav");

と記述しているため、外部SDカードの直下に"wav"という名前でフォルダーを作成し、「test.wav」という名前のモノラルの.wavファイルを置いてください。

※パスとファイル名は、自分の環境に合わせて変更していただいて結構です。

*実機で実行してみる [#m238dcf2]
*実機で実行してみる [#m311c64e]

Android端末を『USBデバック』モードで接続し、eclipseから実行。

もしくは、とりあえずエミュレータで動かし、「bin」以下に作成される「MyHelloOpenAL.apk」をAndroid端末にコピーしインストールして実行します。

問題がなければ、画面の生成時に、指定したwavファイルが読み込まれて音が鳴ります。

※音が鳴るのはアクティビティの生成時なので、画面を回転させた場合にも鳴る。

*最後に [#r7f4a365]
*最後に [#re645ddc]

ここまでの説明で、AndroidでOpenALを使ったオーディオアプリの作成が出来るようになったはずです。

あとは斬新なアイデアを組み込むなり、OpenALを勉強するなりして、面白いアプリを作ってください。

本Wikiでも紹介しているOpenAL、OpenGL、OpenCVを組み合わせれば、大抵のAndroidアプリは簡単に作成できると思います。