今日の一言
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を使ってみる
・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アプリは簡単に作成できると思います。