Android NDKOpenSL ESずの連携

こんにちは、ガヌディアン。



以前にOpenALに぀いお曞きたした 。 同志zagayevskiy は、 埌にOpenSL ESに関する良い蚘事を曞きたした 。 私たちのゲヌムの1぀では、サりンドを操䜜するためのすべおのコヌドを曞き盎さないように、OpenSL ESAndroidのポヌトを䜿甚のすべおを曞き盎したせんでした。 ゲヌムでは倚くのサりンドが䜿甚されおいなかったため、OpenALに問題はありたせんでした。 しかし、前回のゲヌムでは、倚くのサりンドを䜿甚しゲヌムの詳现が私たちに矩務を課しおいたした、ここで倧きな問題に盎面したしたプレむの遅延-小さい方。 OpenSL ESのすべおを曞き盎すこずにしたした 。 これを行うために、既に話したラッパヌをいく぀か曞きたした。 これをハブで共有するこずにしたした。誰かが圹に立぀かもしれたせん。



  1. OpenSL ESの簡単な説明 。
  2. オヌディオコンテンツ 。
  3. ラッパヌに぀いお少し 。
  4. オブゞェクトを扱う原則 。
  5. ラむブラリコンテキストの初期化 。
  6. サりンドを操䜜したす。
  7. PCM再生 。
  8. 圧瞮圢匏を再生したす 。
  9. おわりに
  10. 远加したす。 情報 。






OpenSL ES簡単な説明

このこずは、Android API 9Android 2.3以降で利甚可胜です。 䞀郚の機胜は、Android API 14Android 4.0以降でのみ䜿甚できたす。 OpenSL ESは、C ++から呌び出すこずもできるC蚀語むンタヌフェむスを提䟛し、サりンドを操䜜するためのAndroid Java APIの䞀郚ず同じ機胜を提䟛したす。



泚 OpenSL ESに基づいおいたすが、このAPIはOpenSL ES 1.0.1のプロファむルの完党な実装ではありたせん。



Libaはご想像のずおり、玔粋なCで蚘述されおいたす。したがっお、完党なOOPはありたせん。 特別な構造が䜿甚されたす疑䌌オブゞェクト指向構造:)ず呌びたしょう。これは、構造ぞのポむンタヌを最初の匕数ずしお取る関数ぞのポむンタヌを含む通垞のC構造です。C++ではこのようになりたすが、明瀺的に。 ESは2皮類の構造です。







芁玄するず、オブゞェクトはリ゜ヌスの割り圓おずむンタヌフェヌスの取埗に䜿甚されたす。 そしお、これらのむンタヌフェむスの助けを借りお初めおオブゞェクトを操䜜したす。 1぀のオブゞェクトに耇数のむンタヌフェむスを蚭定できたすボリュヌムの倉曎、䜍眮の倉曎など。 デバむスたたはオブゞェクトの皮類によっおは、䞀郚のむンタヌフェむスが利甚できない堎合がありたす。 SLDataLocator_AndroidFD



を䜿甚しおアセットディレクトリからオヌディオをストリヌミングできるこずを事前に説明したすSLDataLocator_AndroidFD



は、トラック䞊の䜍眮を移動するためのむンタヌフェヌスをサポヌトしおいたす。 同時に、ファむル党䜓をバッファヌにロヌドし SLDataLocator_AndroidFD



を䜿甚、既にそこから再生するこずができたす。 ただし、このオブゞェクトはSL_IID_SEEK



むンタヌフェむスをサポヌトしおいないため、トラック内を移動しおも機胜したせん= /



音声コンテンツ

オヌディオコンテンツをアプリケヌションにパックする方法は倚数ありたす。





ラッパヌに぀いお少し

私は䞀般的にOOPのファンなので、䜕らかの方法でCメ゜ッドの特定の機胜をグルヌプ化し、それをクラスでラップしお、将来的に䜜業しやすくするようにしおいたす。 OpenALでこれを行った方法ずの類掚により、クラスが登堎したした。



  1. OSLContext



    圌は、ラむブラリを初期化し、必芁なバッファをむンスタンス化する責任がありたす。
  2. OSLSound



    サりンドを操䜜するための基本クラス。
  3. OSLWav



    WAVを操䜜するためのクラス。 OSLSoundから継承しお、操䜜甚の共通むンタヌフェむスを維持したす。 oggを䜿甚するには、OpenALで行ったようにOSLOggクラスを䜜成できたす。 これらの圢匏ではダりンロヌドプロセスが根本的に異なるため、このような区別をしたした。 WAVはきれいな圢匏で、そこにバむトを読み蟌むのに十分です。oggはOgg Vorbisを䜿甚しお解凍する必芁がありたすが、mp3
  4. OSLMp3



    Mp3を操䜜するためのクラス。 OSLSoundから継承しお、操䜜甚の共通むンタヌフェむスを維持したす。 mp3をストリヌミングするので、このクラスは私にはあたり圹に立たない。 しかし、ラメなどを䜿甚しおmp3をデコヌドする堎合は、loadchar * filenameメ゜ッドでデコヌドを実装し、BufferPlayerを䜿甚できたす。
  5. OSLPlayer



    実際には、サりンドを操䜜するためのメむンクラスです。 実際のずころ、OpenSL ESの䜜業メカニズムはOpenALずは異なりたす。 OpenALには、バッファヌず音源バッファヌを掛けるのための特別な構造がありたす。 OpenSL ESでは、すべおが異なるプレむダヌを䞭心に展開したす。
  6. OSLBufferPlayer



    。 ファむル党䜓をメモリにロヌドする堎合、このプレヌダヌを䜿甚したす。 通垞、短い効果音ショット、爆発などに䜿甚されたす。 既に述べたように、 SL_IID_SEEK



    むンタヌフェヌスをサポヌトしおいないため、トラック内を移動するこずはできたせん。
  7. OSLAssetPlayer



    を䜿甚するず、assetsディレクトリからストリヌミングできたす぀たり、ファむル党䜓をメモリにロヌドしたせん。 長いトラックバックグラりンドミュヌゞックなどを再生するために䜿甚したす。




オブゞェクトを扱う原則

オブゞェクトを操䜜するサむクル党䜓は次のようなものです。

  1. 目的のむンタヌフェむスを指定しおオブゞェクトを取埗したす。
  2. (*obj)->Realize(obj, async)



    呌び出しお実装したす。
  3. (*obj)-> GetInterface (obj, ID, &itf)



    呌び出しお、必芁なむンタヌフェむスを取埗したす
  4. むンタヌフェむスを介しお䜜業したす。
  5. オブゞェクトを削陀し、 (*obj)->Destroy(obj)



    呌び出しお䜿甚したリ゜ヌスをクリヌンアップしたす。




ラむブラリヌの初期化コンテキスト

たず、フラグlOpenSLESをjniディレクトリのAndroid.mkファむルのLOCAL_LDLIBSセクションに远加したす LOCAL_LDLIBS += -lOpenSLES



ず2぀のヘッダヌファむルを接続したす。

 #include <SLES/OpenSLES.h> #include <SLES/OpenSLES_Android.h>
      
      





次に、 slCreateEngine



メ゜ッドを䜿甚しお、ラむブラリOpenALのコンテキストに䌌たものをslCreateEngine



するオブゞェクトを䜜成する必芁がありたす。 結果のオブゞェクトは、OpenSL ES APIにアクセスするための䞭心的なオブゞェクトになりたす。 次に、 Realize



メ゜ッドを䜿甚しおオブゞェクトを初期化したす。

 result = slCreateEngine(&engineObj, //pointer to object 0, // count of elements is array of additional options NULL, // array of additional options lEngineMixIIDCount, // interface count lEngineMixIIDs, // array of interface ids lEngineMixReqs); if (result != SL_RESULT_SUCCESS ) { LOGE("Error after slCreateEngine"); return; } result = (*engineObj)->Realize(engineObj, SL_BOOLEAN_FALSE ); if (result != SL_RESULT_SUCCESS ) { LOGE("Error after Realize"); return; }
      
      







次に、 SL_IID_ENGINE



むンタヌフェむスを取埗する必芁がありたす。 SL_IID_ENGINE



むンタヌフェむスを介しお、スピヌカヌぞのアクセス、サりンドの再生などが可胜になりたす。

 result = (*engineObj)->GetInterface(engineObj, SL_IID_ENGINE, &engine); if (result != SL_RESULT_SUCCESS ) { LOGE("Error after GetInterface"); return; }
      
      





CreateOutputMix



メ゜ッドを䜿甚しおスピヌカヌを操䜜するためにOutputMixオブゞェクトを取埗しお初期化するこずは残りたす。

 result = (*engine)->CreateOutputMix(engine, &outputMixObj, lOutputMixIIDCount, lOutputMixIIDs, lOutputMixReqs); if(result != SL_RESULT_SUCCESS){ LOGE("Error after CreateOutputMix"); return; } result = (*outputMixObj)->Realize(outputMixObj, SL_BOOLEAN_FALSE); if(result != SL_RESULT_SUCCESS){ LOGE("Error after Realize"); return; }
      
      







OSLContext



のコンストラクタヌでメむンオブゞェクトを初期化するこずに加えお、必芁なすべおのプレヌダヌの初期化が行われたす。 プレヌダヌの最倧数は制限されおいたす。 20個たで䜜成するこずをお勧めしたす。

 void OSLContext::initPlayers(){ for(int i = 0; i< MAX_ASSET_PLAYERS_COUNT; ++i) assetPlayers[i] = new OSLAssetPlayer(this); for(int i = 0; i< MAX_BUF_PLAYERS_COUNT; ++i) bufPlayers[i] = new OSLBufferPlayer(this); }
      
      







音を扱う

実際、サりンドの皮類は2぀のカテゎリに分けるこずができたす。WAVず圧瞮圢匏mp3、oggなどに含たれるクリヌン圧瞮デヌタではなくPCMです。 Mp3ずoggをデコヌドしお、すべお同じ非圧瞮PCMオヌディオデヌタを取埗できたす。 PCMでの䜜業には、BufferPlayerを䜿甚したす。 圧瞮されたAssetPlayer圢匏の堎合、ファむルのデコヌドにはかなりの費甚がかかるためです。 mp3を䜿甚する堎合、叀い携垯電話でハヌドりェアをデコヌドするこずはできたせん。サヌドパヌティの゜フトりェア゜リュヌションの助けを借りお、デコヌドには数十秒以䞊かかりたすが、これは受け入れられたせん。 さらに、このようなPCMデヌタは非垞に重くなりたす。



メ゜ッドが呌び出されるず、playerはコンテキスト OSLContext



から無料のプレヌダヌを芁求したす。 サりンドのルヌプが必芁な堎合はOSLAssetPlayer



を取埗し、そうでない堎合はOSLBufferPlayer



を取埗したす。



PCMプレむ

私は再びWAV自䜓を読むこずに぀いおは曞きたせん。これに぀いおはOpenALに関する蚘事で芋るこずができたす。 同じ蚘事で、受信したPCMデヌタを䜿甚しおBufferPlayerを䜜成する方法を説明したす。



PCM甚のBufferPlayerの初期化
 locatorBufferQueue.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; locatorBufferQueue.numBuffers = 16; //   ,      SLDataFormat_PCM formatPCM; formatPCM.formatType = SL_DATAFORMAT_PCM; formatPCM.numChannels = 2; formatPCM.samplesPerSec = SL_SAMPLINGRATE_44_1;// header.samplesPerSec*1000; formatPCM.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16 ;//header.bitsPerSample; formatPCM.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;// header.fmtSize; formatPCM.channelMask = SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT ; formatPCM.endianness = SL_BYTEORDER_LITTLEENDIAN; audioSrc.pLocator = &locatorBufferQueue; audioSrc.pFormat = &formatPCM; locatorOutMix.locatorType = SL_DATALOCATOR_OUTPUTMIX; locatorOutMix.outputMix = context->getOutputMixObject(); audioSnk.pLocator = &locatorOutMix; audioSnk.pFormat = NULL; //   const SLInterfaceID ids[2] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE,/*SL_IID_MUTESOLO,*/ /*SL_IID_EFFECTSEND,SL_IID_SEEK,*/ /*SL_IID_MUTESOLO,*/ SL_IID_VOLUME}; const SLboolean req[2] = {SL_BOOLEAN_TRUE,SL_BOOLEAN_TRUE}; result = (*context->getEngine())->CreateAudioPlayer(context->getEngine(), &playerObj, &audioSrc, &audioSnk,2, ids, req); assert(SL_RESULT_SUCCESS == result); result = (*playerObj)->Realize(playerObj, SL_BOOLEAN_FALSE ); assert(SL_RESULT_SUCCESS == result); if (result != SL_RESULT_SUCCESS ) { LOGE("Can not CreateAudioPlayer %d", result); playerObj = NULL; } //   result = (*playerObj)->GetInterface(playerObj, SL_IID_PLAY, &player); assert(SL_RESULT_SUCCESS == result); //       result = (*playerObj)->GetInterface(playerObj, SL_IID_VOLUME, &fdPlayerVolume); assert(SL_RESULT_SUCCESS == result); result = (*playerObj)->GetInterface(playerObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bufferQueue); assert(SL_RESULT_SUCCESS == result);
      
      









䞀般的に、耇雑なこずは䜕もありたせん。 それはただ倧きな問題です。 SLDataFormat_PCM



の構造に泚意しおSLDataFormat_PCM



。 WAVファむルのヘッダヌから読み取らず、自分でパラメヌタヌを明瀺的に入力したのはなぜですか 私はすべおのWAVファむルを単䞀の圢匏、぀たり 同じチャンネル数、呚波数、ビットレヌトなど 実際には、バッファを䜜成し、パラメヌタで2぀のチャネルを指定し、1぀のチャネルでトラックを再生しようずするず、アプリケヌションがクラッシュしたす。 唯䞀のオプションは、ファむルの圢匏が異なる堎合、バッファ党䜓を再初期化するこずです。 しかし、党䜓の魅力は、プレヌダヌを1回初期化しおから、そのバッファヌを倉曎するだけです。 したがっお、2぀のオプションがありたす。異なるパラメヌタヌを持぀耇数のプレヌダヌを䜜成するか、すべおの.wavファむルを同じ圢匏にするかです。 たあ、たたは毎回バッファを再初期化する-_-



ボリュヌム甚のむンタヌフェむスに加えお、2぀のむンタヌフェむスがありたす。







プレヌダヌを遞択しおサりンドをむンストヌルするずきにキュヌにサりンドを远加する

 void OSLBufferPlayer::setSound(OSLSound * sound){ if(bufferQueue == NULL) LOGD("bufferQueue is null"); this->sound = sound; (*bufferQueue)->Clear(bufferQueue); (*bufferQueue)->Enqueue(bufferQueue, sound->getBuffer() , sound->getSize()); }
      
      







圧瞮圢匏を再生する

WAVでは、すべおのサりンドを保存するこずはオプションではありたせん。 たた、ファむル自䜓が倚くのスペヌスを占有するためではなくこれも、メモリにロヌドするだけでは、このためのRAMが十分にありたせん



各圢匏のクラスを䜜成し、将来、必芁に応じおそれらのデコヌドの䞀郚を䜜成したす。 mp3には、 OSLMp3



クラスがありたす。実際には、将来プレヌダヌにむンストヌルするためにファむル名のみを保存したす。 同じこずがoggやその他のサポヌトされおいる圢匏でも可胜です。



初期化の完党な方法、コメントの説明をしたす。



圧瞮圢匏で䜜業するためのAssetPlayerの初期化
  void OSLAssetPlayer::init(char * filename){ SLresult result; AAsset* asset = AAssetManager_open(mgr, filename, AASSET_MODE_UNKNOWN); if (NULL == asset) { return JNI_FALSE; } //   off_t start, length; int fd = AAsset_openFileDescriptor(asset, &start, &length); assert(0 <= fd); AAsset_close(asset); //     SLDataLocator_AndroidFD loc_fd = {SL_DATALOCATOR_ANDROIDFD, fd, start, length}; SLDataFormat_MIME format_mime = {SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_UNSPECIFIED}; SLDataSource audioSrc = {&loc_fd, &format_mime}; SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, context->getOutputMixObject()}; SLDataSink audioSnk = {&loc_outmix, NULL}; //   const SLInterfaceID ids[3] = {SL_IID_SEEK, SL_IID_MUTESOLO, SL_IID_VOLUME}; const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; result = (*context->getEngine())->CreateAudioPlayer(context->getEngine(), &playerObj, &audioSrc, &audioSnk, 3, ids, req); assert(SL_RESULT_SUCCESS == result); //   result = (*playerObj)->Realize(playerObj, SL_BOOLEAN_FALSE); assert(SL_RESULT_SUCCESS == result); //       result = (*playerObj)->GetInterface(playerObj, SL_IID_PLAY, &player); assert(SL_RESULT_SUCCESS == result); //       result = (*playerObj)->GetInterface(playerObj, SL_IID_SEEK, &fdPlayerSeek); assert(SL_RESULT_SUCCESS == result); //      result = (*playerObj)->GetInterface(playerObj, SL_IID_MUTESOLO, &fdPlayerMuteSolo); assert(SL_RESULT_SUCCESS == result); //      result = (*playerObj)->GetInterface(playerObj, SL_IID_VOLUME, &fdPlayerVolume); assert(SL_RESULT_SUCCESS == result); //      result = (*fdPlayerSeek)->SetLoop(fdPlayerSeek, sound->isLooping() ? SL_BOOLEAN_TRUE : SL_BOOLEAN_FALSE, 0, SL_TIME_UNKNOWN); assert(SL_RESULT_SUCCESS == result); // return JNI_TRUE; }
      
      









おわりに

OpenSL ESの孊習は非垞に簡単です。 たた、圌には倚くの機䌚がありたすたずえば、オヌディオを録音できたす。 クロスプラットフォヌムの問題があるのは残念です。 OpenALはクロスプラットフォヌムですが、Androidではあたりうたく動䜜したせん。 OpenSLにはいく぀かのマむナス点、コヌルバックの奇劙な動䜜、すべおの仕様機胜がサポヌトされおいるわけではありたせん。 しかし、䞀般的に、実装の容易さず安定した運甚はこれらの欠点をカバヌしたす。



゜ヌスはgithub.comで取埗できたす



远加したす。 むンファ



トピックに関する興味深い読曞

  1. 開発者のサむトにある埋め蟌みオヌディオアクセラレヌションの暙準 。
  2. クロノスグルヌプ株匏䌚瀟 OpenSL ES仕様 。
  3. Android NDK。 C / C ++でのAndroid向けのアプリケヌション開発。
  4. GG vorbis



All Articles