トップ «前の日記(2011/08/02(Tue)) 最新 次の日記(2011/08/10(Wed))»
【ソース+水=麦茶色の何か】

半期 四半期 全カテゴリ

今日の一言


2011/08/07(Sun) カラオケ行った! [長年日記] 1:00現在 27℃

_ [Android][OpenAL][Linux][FC][Debian][Ubuntu][Windows]AndroidでOpenALを使ってオーディオアプリを作成する

参考:

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を使う方法を説明します。

はじめる前に

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

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

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

Windows7(64bit)でAndroidの開発環境を整える

Windows7(64bit)でAndroid NDKを使ってみる

Ubuntu上でAndroidの開発環境を整える

Ubuntu11.04でAndroid NDKを使ってみる

説明はWindows上でcygwinを使った場合で書いていますが、Ubuntu等のLinuxからでも同様の方法で利用できると思います。

「MyHelloOpenAL」プロジェクトを作成する

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

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

Android用に修正されたOpenALのソースコードをダウンロード

こちら( 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をコンパイルための準備

コピーしたフォルダ名を「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の作成

まず<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のコンパイル

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

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

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

$ ndk-build

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

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

  • アクティビティにネイティブインタフェースを定義

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

ネイティブメソッドの作成

まず、<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;
}

ネイティブメソッドをコンパイル

下記の記述を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が作成されるはずです。

実行前の準備

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

上記のプロジェクトでは

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

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

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

実機で実行してみる

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

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

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

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

最後に

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

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

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