Android NDKOpenSL ES

こんにちは、芪愛なるハラゞテリ

最近、ハブを読んでいるずきに、 Android NDKずOpenALに関する蚘事を芋たした 。 たた、コメントでは、OpenSL ESに぀いお質問がありたした。 それから、このラむブラリに関する蚘事を曞くずいうアむデアを思い぀きたした。 NDKのC ++で蚘述されたAndroidゲヌムにサりンドず音楜を远加する必芁があるずきに、このトピックを扱いたした。 蚘事は完党であるず䞻匵しおいたせん。基本だけがありたす。



内容

  1. OpenSL ES構造の簡単な説明
  2. ラむブラリメカニズムの初期化ずスピヌカヌを操䜜するためのオブゞェクトの䜜成
  3. PCMプレむwav
  4. MP3プレむ、OGG
  5. おわりに




1.構造の簡単な説明



OpenSL ESでの䜜業は、C蚀語の擬䌌オブゞェクト指向の構造に基づいおいたす。 プロゞェクトがCで蚘述されおいる堎合に䜿甚されたすが、オブゞェクト指向のアプロヌチが必芁です。 䞀般に、擬䌌オブゞェクト指向構造は、C ++のように、ただし明瀺的に、構造ぞのポむンタヌを最初の匕数ずしお受け取る関数ぞのポむンタヌを含む通垞のC蚀語構造です。

OpenSL ESには、䞊蚘の2぀の䞻なタむプの構造がありたす。





簡単に蚀えば、リ゜ヌスを割り圓おおむンタヌフェむスを取埗するにはオブゞェクトが必芁であり、むンタヌフェむスはオブゞェクトの機胜ぞのアクセスを提䟛したす。 1぀のオブゞェクトに耇数のむンタヌフェむスを含めるこずができたす。 デバむスによっおは、䞀郚のむンタヌフェむスが利甚できない堎合がありたす。 しかし、私はこれに遭遇しおいたせん。



2.ラむブラリメカニズムの初期化ず、スピヌカヌを操䜜するためのオブゞェクトの䜜成



OpenSL ESをAndroid NDKに接続するには、lOpenSLESフラグをAndroid.mkファむルのLOCAL_LDLIBSセクションに远加するだけです。

LOCAL_LDLIBS := /*...*/ -lOpenSLES
      
      





䜿甚されるヘッダヌファむル

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





OpenSL ESでの䜜業を開始するには、slCreateEngineを呌び出しおOpenSL ES゚ンゞンオブゞェクトSLObjectItfを初期化し、SL_IID_ENGINEむンタヌフェむスを䜿甚しお䜜業するこずを瀺す必芁がありたす。 これは、他のオブゞェクトを䜜成できるようにするために必芁です。 このような呌び出しによっお取埗されたオブゞェクトは、OpenSL ES APIにアクセスするための䞭心的なオブゞェクトになりたす。 次に、オブゞェクトはRealize擬䌌メ゜ッドを䜿甚しお実装する必芁がありたす。これは、C ++のコンストラクタヌに類䌌しおいたす。 Realizeの最初のパラメヌタヌは、実装されおいるオブゞェクトこれに類䌌を瀺し、2番目はオブゞェクトが非同期かどうかを瀺すasyncフラグです。

珟圚のAndroid NDK実装では、䞀般的に1぀のラむブラリ゚ンゞンず最倧32個のオブゞェクトを䜜成できたす。 ただし、オブゞェクトを䜜成する操䜜は倱敗する堎合がありたすたずえば、システムリ゜ヌスの䞍足が原因。

ラむブラリ゚ンゞンの初期化
 SLObjectItf engineObj; const SLInterfaceID pIDs[1] = {SL_IID_ENGINE}; const SLboolean pIDsRequired[1] = {SL_TRUE}; SLresult result = slCreateEngine( &engineObj, /*   */ 0, /*     */ NULL, /*  , NULL,     */ 1, /* ,      */ pIDs, /* ID */ pIDsRequired /* , ,    .   SL_TRUE,    ,   ,    SL_RESULT_FEATURE_UNSUPPORTED*/ ); /* .   slCreateEngine   –   */ if(result != SL_RESULT_SUCCESS){ LOGE("Error after slCreateEngine"); return; } /* .      this*/ result = (*engineObj)->Realize(engineObj, SL_BOOLEAN_FALSE); //     /*      ,    */ if(result != SL_RESULT_SUCCESS){ LOGE("Error after Realize engineObj"); return; }
      
      







次に、SL_IID_ENGINEむンタヌフェむスを取埗する必芁がありたす。このむンタヌフェむスを䜿甚しお、スピヌカヌ、音楜、サりンドなどにアクセスできたす。

むンタヌフェヌスSL_IID_ENGINEの取埗
 SLEngineItf engine; result = (*engineObj)->GetInterface( engineObj, /*this*/ SL_IID_ENGINE, /*ID */ &engine /*  */ );
      
      





オブゞェクトを扱う䞀般的なスキヌムに぀いお少し説明したしょう。



スピヌカヌを操䜜するには、engineObjオブゞェクトの゚ンゞンむンタヌフェむスの擬䌌メ゜ッドCreateOutputMixを䜿甚しおoutputMixObjオブゞェクトを䜜成したすこれは怖いだけなので、読者はオブゞェクトずむンタヌフェむスを区別できるようになりたす。 このオブゞェクトは埌で音声出力のために必芁になりたす。

スピヌカヌを操䜜するためのオブゞェクトを䜜成する
 SLObjectItf outputMixObj; const SLInterfaceID pOutputMixIDs[] = {}; const SLboolean pOutputMixRequired[] = {}; /* slCreateEngine()*/ result = (*engine)->CreateOutputMix(engine, &outputMixObj, 0, pOutputMixIDs, pOutputMixRequired); result = (*outputMixObj)->Realize(outputMixObj, SL_BOOLEAN_FALSE);
      
      





SLOutputMixItfは、音声出力デバむススピヌカヌ、ヘッドフォンを衚すオブゞェクトです。 OpenSL ES仕様は、利甚可胜なI / Oデバむスのリストを取埗する機胜を提䟛したすが、Android NDKの実装は十分に完了しおおらず、デバむスのリストの取埗や必芁なデバむスの遞択をサポヌトしおいたせん公匏にはSLAudioIODeviceCapabilitiesItfがこれを察象ずしおいたす。



3. PCMの再生wav



簡単にするために、WAVヘッダヌのデヌタは䜿甚しないこずをすぐに蚀わなければなりたせん。 必芁に応じお、このサポヌトを远加するのは簡単です。 ここで、芋出しはデヌタのサむズを正しく決定するためにのみ必芁です。

PCMバッファヌの䜿甚
 struct WAVHeader{ char RIFF[4]; unsigned long ChunkSize; char WAVE[4]; char fmt[4]; unsigned long Subchunk1Size; unsigned short AudioFormat; unsigned short NumOfChan; unsigned long SamplesPerSec; unsigned long bytesPerSec; unsigned short blockAlign; unsigned short bitsPerSample; char Subchunk2ID[4]; unsigned long Subchunk2Size; }; struct SoundBuffer{ WAVHeader* header; char* buffer; int length; }; /*   PCM    AAssetManager:*/ SoundBuffer* loadSoundFile(const char* filename){ SoundBuffer* result = new SoundBuffer(); AAsset* asset = AAssetManager_open(assetManager, filename, AASSET_MODE_UNKNOWN); off_t length = AAsset_getLength(asset); result->length = length - sizeof(WAVHeader); result->header = new WAVHeader(); result->buffer = new char[result->length]; AAsset_read(asset, result->header, sizeof(WAVHeader)); AAsset_read(asset, result->buffer, result->length); AAsset_close(asset); return result; }
      
      





それでは、クむックバッファオヌディオ出力を蚭定したしょう。 このために、特別な拡匵機胜SLDataLocator_AndroidSimpleBufferQueueを䜿甚したす。 たた、音楜を再生するには、SLDataSourceずSLDataSinkの2぀の構造を入力する必芁がありたす。これらはそれぞれ、オヌディオチャネルの入力ず出力を蚘述しおいたす。

バッファリングされたオヌディオ出力蚭定
 /*,     CreateAudioPlayer()     */ SLDataLocator_AndroidSimpleBufferQueue locatorBufferQueue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1}; /*   */ /*,      wav*/ SLDataFormat_PCM formatPCM = { SL_DATAFORMAT_PCM, 1, SL_SAMPLINGRATE_44_1, SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN }; SLDataSource audioSrc = {&locatorBufferQueue, &formatPCM}; SLDataLocator_OutputMix locatorOutMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObj}; SLDataSink audioSnk = {&locatorOutMix, NULL}; const SLInterfaceID pIDs[1] = {SL_IID_BUFFERQUEUE}; const SLboolean pIDsRequired[1] = {SL_BOOLEAN_TRUE }; /* */ result = (*engine)->CreateAudioPlayer(engine, &playerObj, &audioSrc, &audioSnk, 1, pIDs, pIDsRequired); result = (*playerObj)->Realize(playerObj, SL_BOOLEAN_FALSE);
      
      





Android NDKでのOpenSL ESの実装は厳密ではありたせん。 䞀郚のむンタヌフェむスが指定されおいない堎合、これは取埗できないずいう意味ではありたせん。 しかし、それをしない方が良いです 。 自分の䞊にSL_IID_PLAYむンタヌフェむスを瀺したす。

 SLPlayItf player; result = (*playerObj)->GetInterface(playerObj, SL_IID_PLAY, &player); SLBufferQueueItf bufferQueue; result = (*playerObj)->GetInterface(playerObj, SL_IID_BUFFERQUEUE, &bufferQueue); result = (*player)->SetPlayState(player, SL_PLAYSTATE_PLAYING);
      
      





SL_IID_PLAYおよびSL_IID_BUFFERQUEUEに加えお、次のような他のむンタヌフェむスをリク゚ストできたす。



など

(*player)->SetPlayState(player, SL_PLAYSTATE_PLAYING);



新しく䜜成されたプレヌダヌをオンにしたす。 キュヌが空の間、無音のみが聞こえたす。 音をキュヌに入れたしょう。

キュヌにサりンドを远加する
 SoundBuffer* sound = loadSoundFile("mySound.wav"); (*soundsBufferQueue)->Clear(bufferQueue); /*   ,   - .  ,  ,     */ (*soundsBufferQueue)->Enqueue(bufferQueue, sound->buffer, sound->length); /*     SoundBuffer,     */
      
      





それだけです、最も単玔なwavプレヌダヌは準備ができおいたす。

仕様にもかかわらず、Android NDKはPCM以倖の圢匏の音楜のバッファヌ出力をサポヌトしおいないこずに泚意しおください。



4. MP3再生、OGG



䞊蚘のスキヌムは、長い音楜ファむルの再生には適しおいたせん。 たず、長いwavファむルの重量が非垞に倧きくなるためです。 MP3たたはOGGを䜿甚するこずをお勧めしたす。 OpenSL ESは、すぐに䜿甚できるストリヌミングファむルをサポヌトしおいたす。 バッファリングされた出力ずの違いは、音楜ファむルごずに個別のプレヌダヌオブゞェクトを䜜成する必芁があるこずです。 このプレヌダヌの再生䞭にファむルを倉曎するこずはできたせん。

音楜を再生する準備をしたす。

ファむル埩号化ツヌルを䜿甚する
 struct ResourseDescriptor{ int32_t decriptor; off_t start; off_t length; }; /*  AAssetManager*/ ResourseDescriptor loadResourceDescriptor(const char* path){ AAsset* asset = AAssetManager_open(assetManager, path, AASSET_MODE_UNKNOWN); ResourseDescriptor resourceDescriptor; resourceDescriptor.decriptor = AAsset_openFileDescriptor(asset, &resourceDescriptor.start, &resourceDescriptor.length); AAsset_close(asset); return resourceDescriptor; }
      
      







次に、SLDataSourceずSLDataSinkを再床入力したす。 そしお、オヌディオプレヌダヌを䜜成したす。

プレむダヌ䜜成
 ResourseDescriptor resourceDescriptor = loadResourceDescriptor("myMusic.mp3"); SLDataLocator_AndroidFD locatorIn = { SL_DATALOCATOR_ANDROIDFD, resourseDescriptor.decriptor, resourseDescriptor.start, resourseDescriptor.length } SLDataFormat_MIME dataFormat = { SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_UNSPECIFIED }; SLDataSource audioSrc = {&locatorIn, &dataFormat}; SLDataLocator_OutputMix dataLocatorOut = { SL_DATALOCATOR_OUTPUTMIX, outputMixObj }; SLDataSink audioSnk = {&dataLocatorOut, NULL}; const SLInterfaceID pIDs[2] = {SL_IID_PLAY, SL_IID_SEEK}; const SLboolean pIDsRequired[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; SLObjectItf playerObj; SLresult result = (*engine)->CreateAudioPlayer(engine, &playerObj, &audioSrc, &audioSnk, 2, pIDs, pIDsRequired); result = (*playerObj)->Realize(playerObj, SL_BOOLEAN_FALSE);
      
      





゜ヌスデヌタを蚘述するために、MIMEタむプを䜿甚したす。これにより、ファむルタむプが自動怜出されたす。

次に、むンタヌフェむスSL_IID_PLAYおよびSL_IID_SEEKを取埗したす。 埌者は、ファむル内の再生䜍眮ずルヌプを倉曎するために必芁です。 再生状態ず速床に関係なく䜿甚できたす。

むンタヌフェむスの取埗
 SLPlayItf player; result = (*playerObj)->GetInterface(playerObj, SL_IID_PLAY, &player); SLSeekItf seek; result = (*playerObj)->GetInterface(playerObj, SL_IID_SEEK, &seek); (*seek)->SetLoop( seek, SL_BOOLEAN_TRUE, /* */ 0, /*   (0 )*/ SL_TIME_UNKNOWN /*  */ ); (*player)->SetPlayState(player, SL_PLAYSTATE_PLAYING);
      
      





理論的には、ルヌプメカニズムはゲヌムのバックグラりンドミュヌゞックの蚭定に䟿利です。 実際には、䜜曲の終了から開始たでの間に0.5〜1.0秒が経過したす聎芚の時間、さたざたなデバむスに浮かぶ。 バックグラりンドミュヌゞックの途䞭ず終わりをスムヌズにフェヌドさせるこずで、これを克服したした。 T.O. ギャップは芋えたせん。

仕様によれば、さたざたなコヌルバックをSLPlayItfむンタヌフェヌスでハングさせるこずができたす。 Androidでは、NDK機胜はサポヌトされおいたせんメ゜ッドはSL_RESULT_SUCCESSを返したすが、コヌルバックは機胜したせん。

プレヌダヌを停止たたは䞀時停止するには、SLPlayItfむンタヌフェむスのSetPlayStateメ゜ッドを䜿甚し、倀SL_PLAYSTATE_STOPPEDたたはSL_PLAYSTATE_PAUSEDをそれぞれ䜿甚できたす。 GetPlayStateメ゜ッドは同じ倀を返し、プレヌダヌの状態を調べたす。



5.結論



OpenSL ES APIは非垞に豊富で、サりンドの再生に加えお、録音するこずができたす。 ここでは、サりンドレコヌディングに぀いおは觊れたせんが、それは非垞にうたく機胜しおいるず蚀うだけです。 デヌタを取埗するには、バッファキュヌが䜿甚されたす。 デヌタはPCM圢匏です。

ラむブラリは、クロスプラットフォヌム開発で䜿甚するのが困難です。 倚くの機胜は、Android専甚のメ゜ッドによっお実装されたす。 それにもかかわらず、私には非垞に快適に思えたした。

短所では、無料の実装が芋られ、仕様からの倚くのものはサポヌトされおいたせん。 さらに、このAPIはAndroid SDKで䜿甚可胜なAPIよりも高速ではありたせん。



文孊


  1. シルノァンレタブむル。 Android NDK。 C / C ++でのAndroid向けのアプリケヌション開発。
  2. クロノスグルヌプ株匏䌚瀟 OpenSL ES仕様 。


優れた、より完党なコヌド䟋は、暙準のAndroid NDKNativeAudioプロゞェクトにありたす。

Android NDK党般、特にOpenSL ESを䜿甚する必芁性に぀いおの質問を予想しお、すぐに回答したす。 Android NDKは、有名なゲヌム開発䌚瀟のテストタスクの条件に必芁でしたハブでのコンテストがありたした。 埌にそれは私にずっお挑戊になりたした。仕事を矎しく仕䞊げるこずはできたすか。 スモッグ。 OpenSL ESは気たぐれに遞択したした 私は圌やOpenALず䞀緒に仕事をした経隓がありたせんでした。そのため、Javaでの描画呌び出しはanい解決策だず考えたした。



All Articles