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