OpenALを䟋ずしお䜿甚したAndroid NDKの基本

こんにちは、Habrausers様



最近、私はAndroid甚のアプリケヌション、特にゲヌムの開発を行っおいたす。 そのため、あるプロゞェクトではAndroid ndkを䜿甚する必芁がありたした。 原則ずしお、ネむティブでの䜜業のすべおの困難ずニュアンスを1぀の蚘事で怜蚎するこずは䞍可胜であるため、この蚘事ではndkの簡単な玹介を曞くこずにしたした。

そしお、この蚘事を初心者だけでなく興味深いものにするために、OpenALずWAV、OGG圢匏での䜜業方法を瀺したす。







はじめに



私にずっおは、環境のセットアップに぀いお倚くを曞く䟡倀はありたせん。 開発しおいる環境Eclipse、IntelliJ IDEAなどに関係なく、セットアップは非垞に簡単です。

  1. Android NDK自䜓 。
  2. WInでビルドするには、 Cygwinが必芁です。
  3. 同じEclipseのプラグむン CDT 。


圓然、すでにADT、JDKが必芁です。



なぜNDKが必芁なのですか






JavaからC ++コヌドを呌び出す



䞀般的に、すべおが非垞に簡単で、䞻な手順は次のずおりです。

  1. C ++でファむルを䜜成し、゚クスポヌト甚のメ゜ッドを定矩したす。
  2. .mkファむルの䜜成。
  3. ラむブラリ生成。
  4. Javaでラむブラリを接続したす。






Makefile.mkに぀いおペむントしたせん。 ここで圌に぀いお読むこずができたす 。 その䞊、habrにはBubaVVの.mkファむルの䜜業に関する良い蚘事がありたす 。



ラむブラリに぀いおは、ndkからここで読むこずができたす 。



C ++ファむルの䜜成


Javaから呌び出す゚クスポヌトのメ゜ッドを定矩する必芁がありたす。 䟋ずしお、アプリケヌションの起動時に、OpenALに音楜をロヌドしたす。 これを行うには、メ゜ッドを定矩したす。

JNIEXPORT void JNICALL Java_ru_suvitruf_androidndk_tutorial4_MainActivity_loadAudio(JNIEnv *pEnv, jobject pThis, jobject pNativeCallListener, jobject assetManager);
      
      







これらはすべおペンで蚘述したすが、 javahの自動生成には䟿利なナヌティリティがありたす。



次に、それを実装する必芁がありたすが、それに぀いおは埌で詳しく説明したす。

名前に぀いお少し
メ゜ッドの名前に぀いお少し述べる䟡倀がありたす。 Java_は必須のプレフィックスです。 ru_suvitruf_androidndk_tutorial4。ru.suvitruf.androidndk.tutorial4パッケヌゞがあるため、クラスずメ゜ッドの名前はJava偎にありたす。 各関数には匕数ずしおJNIEnv *がありたす。これは、Javaを操䜜するためのむンタヌフェヌスであり、Javaメ゜ッドを呌び出しおJavaオブゞェクトを䜜成できたす。 2番目の必須パラメヌタヌは、メ゜ッドが静的かどうかに応じお、 jobjectたたはjclassです。 メ゜ッドが静的な堎合、匕数はjclass型メ゜ッドが宣蚀されおいるオブゞェクトのクラスぞの参照であり、静的でない堎合-jobject-メ゜ッドが呌び出されたオブゞェクトぞの参照です。





Javaのラむブラリ接続


ラむブラリを生成したら、Javaに接続する必芁がありたす。

 static { System.loadLibrary("AndroidNDK"); }
      
      







そしお、C ++コヌドず同じ名前のメ゜ッドを定矩したす。

 //  native public void loadAudio(NativeCalls nativeCallListener, AssetManager mng);
      
      







このような呌び出し

 loadAudio(activity, activity.getResources().getAssets());
      
      







C ++からのJava呌び出し



もう少し耇雑ですが、それほど怖くない。 必芁なもの

  1. 呌び出すクラスメ゜ッドJavaを定矩したす。
  2. 目的のクラスの蚘述子を取埗したすC ++で。
  3. メ゜ッドのシグネチャを説明したす。
  4. メ゜ッド識別子リンクを取埗したす。
  5. 目的のオブゞェクトでメ゜ッドを呌び出したす。




もちろん、クラスのメ゜ッドを簡単に定矩できたすが、むンタヌフェむスを䜿甚するこずをお勧めしたす。 その埌、別のクラスで䜜業する堎合、ネむティブコヌドを倉曎する必芁はありたせん。



䟋ずしお、たった1぀のメ゜ッドでむンタヌフェヌスを䜜成したす。

 public interface NativeCalls { public void sendLog(String result); }
      
      







CalledFromWrongThreadExceptionず適切なむンタヌフェむスの実装
ああ、これらの流れ。 問題は、別のストリヌムからのビュヌに圱響を䞎えられないこずです。 したがっお、むンタヌフェむスの実装党䜓は次のようになりたす。

 protected Handler handler = new Handler() { @Override public void handleMessage(Message msg) { showResult(msg.getData().getString("result")); } }; public void showResult(String result){ ((TextView) findViewById(R.id.log)). setText(((TextView) findViewById(R.id.log)).getText()+result+"\n"); } //    @Override public void sendLog(String result){ Message msg = new Message(); Bundle data = new Bundle(); data.putString("result", result); msg.setData(data); handler.sendMessage(msg); }
      
      









スレッドに問題はないかもしれたせんが、私たちの堎合、アプリケヌションを起動するずきに、リ゜ヌスをロヌドするための別のスレッドを䜜成するので、質問は私たちにずっお重芁です。



次のクラスは、ネむティブC ++コヌドのJavaむンタヌフェむスに察応したす。

NativeCallListener
 class NativeCallListener { public: NativeCallListener(JNIEnv* pJniEnv, jobject pWrapperInstance); NativeCallListener() {} //  //   Java  void sendLog(jobject log); //   void destroy(); ~NativeCallListener(){ } void loadAudio(); //void play(); //void playOGG(); ALCdevice* device; ALCcontext* context; private: JNIEnv* getJniEnv(); //   jmethodID sendLogID; //   jobject mObjectRef; JavaVM* mJVM; ALuint soundWAV; ALuint soundOGG; void load(); void clean(); };
      
      







これで、蚘事の最初の郚分にヘッダヌがあったloadAudioメ゜ッドの実装を衚瀺できたす。

 JNIEXPORT void JNICALL Java_ru_suvitruf_androidndk_tutorial4_MainActivity_loadAudio(JNIEnv *pEnv, jobject pThis, jobject pNativeCallListener, jobject assetManager) { listener = NativeCallListener(pEnv, pNativeCallListener); mgr = AAssetManager_fromJava(pEnv, assetManager); listener.loadAudio(); }
      
      







クラスコンストラクタヌで、クラス蚘述子を保存し、そのメ゜ッドぞの参照を取埗したす。

 NativeCallListener::NativeCallListener(JNIEnv* pJniEnv, jobject pWrappedInstance) { pJniEnv->GetJavaVM(&mJVM); mObjectRef = pJniEnv->NewGlobalRef(pWrappedInstance); jclass cl = pJniEnv->GetObjectClass(pWrappedInstance); // ,       Java sendLogID = pJniEnv->GetMethodID(cl, "sendLog", "(Ljava/lang/String;)V"); }
      
      







これで、Javaメ゜ッドを呌び出すこずができたす。

 void NativeCallListener::sendLog(jobject log) { JNIEnv* jniEnv = getJniEnv(); jniEnv->CallIntMethod(mObjectRef, sendLogID, log); }
      
      







AAssetManager



以前は、アプリケヌションリ゜ヌスを操䜜するためにオヌプン゜ヌスのlibzipラむブラリが䜿甚されおいたした。

APIのバヌゞョン2.3では、Android ndkにはC ++コヌドから盎接アセットディレクトリを操䜜するための優れたクラスがありたす。

この方法は、stdio.hのファむルを操䜜する方法に䌌おいたす。 fopenの代わりにAAssetManager_open、freadの代わりにAAsset_read、fcloseの代わりにAAsset_close。



私は圌のために小さなラッパヌを曞きたした。 䞀般に䜜業は通垞のFILEず同じなので、ここにコヌドを挿入したせん。



OpenALを䜿甚する



この蚘事はすでに非垞に倧きな蚘事ですが、私は最も興味深い郚分を始めおいたせん。 これを蚱しおください...



準備する


たず、OpenALをビルドする必芁がありたす。 これはWAVを䜿甚するのに十分ですが、OGGを䜿甚する必芁がありたす。 OGGにはTremorデコヌダヌが必芁です。



サりンドに぀いおは、必芁なメ゜ッドを䜿甚しおラッパヌを䜜成したした。 ここのすべおのコヌドは意味をなさないので、最も興味深いもの、぀たりダりンロヌドを匷調したす。



wavファむルを読む


最初に、ヘッダヌの構造を説明する必芁がありたす。

BasicWAVEHeader
 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;
      
      









今読む

 void OALWav::load(AAssetManager *mgr, const char* filename){ this->filename = filename; this->data = 0; //  this->data = this->readWAVFull(mgr, &header); //  getFormat(); // OpenAL  createBufferFromWave(data); source = 0; alGenSources(1, &source); alSourcei(source, AL_BUFFER, buffer); }
      
      







readWAVFull
 char* OALWav::readWAVFull(AAssetManager *mgr, BasicWAVEHeader* header){ char* buffer = 0; AAssetFile f = AAssetFile(mgr, filename); if (f.null()) { LOGE("no file %s in readWAV",filename); return 0; } int res = f.read(header,sizeof(BasicWAVEHeader),1); if(res){ if (!( //    . //   ,    . //          =/ 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(f.read(buffer,header->dataSize,1)){ f.close(); return buffer; } free(buffer); } } } f.close(); return 0; }
      
      







WAVに぀いお蚀わなければならないこずがありたす。 時々、PC䞊のファむルは正垞にリッスンしおいるように芋えたすが、OpenALで䜜業しおいるずきに゚ラヌが発生したす。 これは、砎損したヘッダヌの結果です。 通垞はdataSizeで 、ヘッダヌ䟋ずしおロゎに䜕らかの皮類のゎミを曞き蟌む倚くのコンバヌタヌに䌚いたした。 なぜ機胜しないのに、PCで再生するのですか

オヌディオデヌタ自䜓は、ヘッダヌずdataSizeのサむズの埌に保存されたす 。 このフィヌルドに問題がある堎合、゚ラヌが発生したす。 あなたは本圓に額のサむズを蚈算するこずができたす。 デヌタサむズ=ファむルサむズ-ヘッダヌサむズ 。 したがっお、プレむダヌはヘッダヌからではなく、枛算によっおデヌタのサむズを取埗するず思いたす。



WAVを䜿甚する堎合、圢匏は圧瞮されおいないため、すべおが単玔に思えたす。 .Oggを䜿甚する堎合、事態はより耇雑になりたす。



Oggファむルを読む


WAVず比范したOggの機胜は䜕ですか これは圧瞮圢匏です。 そのため、OpenALバッファヌぞのデヌタの曞き蟌み方法の前に、デヌタをデコヌドする必芁がありたす。

キャッチは、デフォルトでFILEからのVorbisストリヌムなので、デヌタを操䜜するためにすべおのコヌルバックメ゜ッドをオヌバヌラむドする必芁がありたす。



コヌルバック
 static size_t read_func(void* ptr, size_t size, size_t nmemb, void* datasource) { unsigned int uiBytes = Min(suiSize - suiCurrPos, (unsigned int)nmemb * (unsigned int)size); memcpy(ptr, (unsigned char*)datasource + suiCurrPos, uiBytes); suiCurrPos += uiBytes; return uiBytes; } static int seek_func(void* datasource, ogg_int64_t offset, int whence) { if (whence == SEEK_SET) suiCurrPos = (unsigned int)offset; else if (whence == SEEK_CUR) suiCurrPos = suiCurrPos + (unsigned int)offset; else if (whence == SEEK_END) suiCurrPos = suiSize; return 0; } static int close_func(void* datasource) { return 0; } static long tell_func(void* datasource) { return (long)suiCurrPos; }
      
      









今、あなたは読む必芁がありたす

Oggを読む
 void OALOgg::getInfo(unsigned int uiOggSize, char* pvOggBuffer){ //   ov_callbacks callbacks; callbacks.read_func = &read_func; callbacks.seek_func = &seek_func; callbacks.close_func = &close_func; callbacks.tell_func = &tell_func; suiCurrPos = 0; suiSize = uiOggSize; int iRet = ov_open_callbacks(pvOggBuffer, &vf, NULL, 0, callbacks); //  vi = ov_info(&vf, -1); uiPCMSamples = (unsigned int)ov_pcm_total(&vf, -1); } void * OALOgg::ConvertOggToPCM(unsigned int uiOggSize, char* pvOggBuffer) { if(suiSize == 0){ getInfo( uiOggSize, pvOggBuffer); current_section = 0; iRead = 0; uiCurrPos = 0; } void* pvPCMBuffer = malloc(uiPCMSamples * vi->channels * sizeof(short)); //  do { iRead = ov_read(&vf, (char*)pvPCMBuffer + uiCurrPos, 4096, €t_section); uiCurrPos += (unsigned int)iRead; } while (iRead != 0); return pvPCMBuffer; } void OALOgg::load(AAssetManager *mgr, const char* filename){ this->filename = filename; char* buf = 0; AAssetFile f = AAssetFile(mgr, filename); if (f.null()) { LOGE("no file %s in readOgg",filename); return ; } buf = 0; buf = (char*)malloc(f.size()); if (buf){ if(f.read(buf,f.size(),1)){ } else { free(buf); f.close(); return; } } char * data = (char *)ConvertOggToPCM(f.size(),buf); f.close(); if (vi->channels == 1) format = AL_FORMAT_MONO16; else format = AL_FORMAT_STEREO16; alGenBuffers(1,&buffer); alBufferData(buffer,format,data,uiPCMSamples * vi->channels * sizeof(short),vi->rate); source = 0; alGenSources(1, &source); alSourcei(source, AL_BUFFER, buffer); }
      
      









アプリケヌションをロヌドするずき、C ++メ゜ッドloadAudioを呌び出したす。これは、サりンドをロヌドするNativeCallListenerのロヌドを呌び出したす。

 void NativeCallListener:: load(){ oalContext = new OALContext(); //sound = new OALOgg(); sound = new OALWav(); char * fileName = new char[64]; strcpy(fileName, "audio/industrial_suspense1.wav"); //strcpy(fileName, "audio/Katatonia - Deadhouse_(piano version).ogg"); sound->load(mgr,fileName); }
      
      





sound



OALSound



たす。 WAVずOggを操䜜するために、それを継承するクラスがありたす。 基本クラスのメ゜ッドvirtual void load(AAssetManager *mgr, const char* filename)= 0;



オヌバヌラむドするためのロヌド実装を蚘述するだけですvirtual void load(AAssetManager *mgr, const char* filename)= 0;





これにより、サりンドず䜜業を統合できたす。



おわりに



繰り返しになりたすが、この蚘事が非垞に膚倧であったこずをおaびしたす。 提瀺された実装を䜿甚するず、プラットフォヌムに関係なくサりンドを操䜜できたす。 iOSおよびAndroid甚のゲヌム゚ンゞンを䜜成しおいる堎合を考えおみたしょう。



ここにはニュアンスがありたす-オヌディオは完党にロヌドされたす。 したがっお、音に぀いおは、この解決策は玠晎らしいですが、音楜に぀いおはそうではありたせん。 解凍した.oggの歌がどれだけのメモリを消費するか想像しおみおください。 したがっお、誰かがこの゜リュヌションに基づいお、バッファぞの党負荷ではなく、ストリヌミングでオヌディオ再生を曞き蟌むのは玠晎らしいこずです。



゜ヌスコヌド


プロゞェクトはEclipseで曞かれおいたす。 ゜ヌスはgithubで衚瀺できたす。



PS批刀ずアドバむスを楜しみにしおいたす

PPSでは、テキストに文法的な誀りが芋぀かった堎合は、pmで曞くこずをお勧めしたす。



All Articles