麦芽を支える技術

麦芽(ばくが、英語:malt)とは、麦、特に大麦の種子を発芽させたもので、ビール、ウイスキー、水飴の原料となる。(Wikipediaより)

Android NDKでOpenSL ESを利用する

AndroidアプリでOpenSL ESを利用してMP3ファイルの再生を行う必要があったのでいろいろ調べてみたものの、日本語の解説サイトがほとんど見つからないのと、いくら「OpenSL ES」でググっても「OpenGL」か「OpenSSL」ばかりヒットしてしまうので、昔調べた時に別なとこにメモってたやつをここに移行。

OpenSL ES概要

一から解説しないけど、以下の図が全体的な要点が綺麗にまとまっているので公式ドキュメントより引用。

f:id:asmz0:20160312222601p:plain

引用元:http://www.khronos.org/registry/sles/specs/OpenSL_ES_Specification_1.0.1.pdf

音声再生までのざっくりとした流れは以下の様な感じ。

  1. 処理全体を制御する「Engine」の生成
  2. Engineより最終的な出力デバイスとなる「Output Mix」を生成
  3. 音声入力元「DataSource」、出力先「DataSink」の設定
  4. 音声再生を行う「AudioPlayer」の生成
  5. AudioPlayerへ各種操作を行う「Interface」を取得
  6. 再生Interfaceより再生実行
  7. 再生が全て完了したら「Engine」を解放

処理全体を制御する「Engine」の生成

まず最初にすべての処理で必要になるエンジンを生成します。

なおOpenSL ESではエンジンに限らずオブジェクト生成後に「リアライズ」という処理でそのオブジェクトを実体化(有効化)する必要があるため、ここでリアライズを行います。

またその後のAPIを利用するために、ここでエンジンのインタフェースを取得しておきます。

SLresult result;
 
//  エンジンの生成
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
 
//  エンジンをリアライズ
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
 
//  エンジンのインタフェースを取得
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE,
    &engineEngine);

Engineより最終的な出力デバイスとなる「Output Mix」を生成

エンジンを生成したら、以下の通り最終的な出力デバイスとなる「OutputMix」オブジェクトが生成出来るようになるので、ここで生成します。

こちらもオブジェクト生成後にリアライズを行います。

// 出力デバイスとなる「Output Mix」を生成
const SLInterfaceID ids[1] = {SL_IID_PLAYBACKRATE};
const SLboolean req[1] = {SL_BOOLEAN_FALSE};
result = (*engineEngine)->CreateOutputMix(engineEngine,
    &outputMixObject, 1, ids, req);
 
// Output Mixをリアライズ
result = (*outputMixObject)->Realize(outputMixObject,
    SL_BOOLEAN_FALSE);

音声入力元「DataSource」、出力先「DataSink」の設定

DataSourceには音声の入力元を指定します。指定の仕方としてはURL指定の他に、Androidの場合はプロジェクトのAssetsにリソースファイルとしてMP3を配置しておくことで、それを利用することも可能です。

  • URL指定の場合
// Java側から渡されたURL文字列を変換
const char *utf8 = (*env)->GetStringUTFChars(env, uri, NULL);
 
// DataSource設定
SLDataLocator_URI loc_uri = { SL_DATALOCATOR_URI, (SLchar *) utf8 };
SLDataFormat_MIME format_mime = { SL_DATAFORMAT_MIME, NULL,
    SL_CONTAINERTYPE_UNSPECIFIED };
SLDataSource audioSrc = { &loc_uri, &format_mime };
  • Assetsリソース利用の場合
// Java側から渡されたファイル名を変換
const char *utf8 = (*env)->GetStringUTFChars(env, filename, NULL);
 
// AssetManagerを用いてファイルオープン
AAssetManager* mgr = AAssetManager_fromJava(env, assetManager);
AAsset* asset = AAssetManager_open(mgr, utf8, AASSET_MODE_UNKNOWN);
 
// ファイルディスクリプタオープン
off_t start, length;
int fd = AAsset_openFileDescriptor(asset, &start, &length);
 
// DataSource設定
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 };

DataSinkは先に生成した「Output Mix」を設定します。

// DataSinkの設定
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX,
    outputMixObject};
SLDataSink audioSnk = {&loc_outmix, NULL};

音声再生を行う「AudioPlayer」の生成

これまでに生成したエンジン、DataSource、DataSinkを指定してオーディオプレイヤーを生成、リアライズします。

// AudioPlayerの生成
const SLInterfaceID ids[2] = {SL_IID_SEEK, SL_IID_VOLUME};
const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
result = (*engineEngine)->CreateAudioPlayer(engineEngine,
    &audioPlayerObject,
    &audioSrc,
    &audioSnk,
    2, ids, req);
 
// AudioPlayerのリアライズ
result = (*audioPlayerObject)->Realize(audioPlayerObject,
   SL_BOOLEAN_FALSE);

AudioPlayerへ各種操作を行う「Interface」を取得

AudioPlayerに対する操作を行うため、インタフェースを取得します。 ここでは再生するための「再生インタフェース」の他、シークに使用する「SEEKインタフェース」、音量コントロールのための「ボリュームインタフェース」を取得しています。

// 再生インタフェースを取得
result = (*audioPlayerObject)->GetInterface(audioPlayerObject,
    SL_IID_PLAY, &audioPlayerPlay);
 
// SEEKインタフェースを取得
result = (*audioPlayerObject)->GetInterface(audioPlayerObject,
    SL_IID_SEEK, &audioPlayerSeek);
 
// ボリュームインタフェースを取得
result = (*audioPlayerObject)->GetInterface(audioPlayerObject,
    SL_IID_VOLUME, &audioPlayerVolume);

再生インタフェースより再生実行

ここまでで音声データの準備は完了となるので、先に取得した再生インタフェースを利用して再生処理を実行することで再生が開始されます。

// 再生インタフェースより再生実行
result = (*audioPlayerPlay)->SetPlayState(audioPlayerPlay,
    SL_PLAYSTATE_PLAYING);

処理が全て完了したら「Engine」を解放

再生に関する処理が全て完了してオブジェクトが不要になったら、オーディオプレイヤー、Output Mix、エンジンオブジェクトを解放します。

// 念のため再生停止
setPlayState(SL_PLAYSTATE_STOPPED);
 
// Audio Player解放
if (audioPlayerObject != NULL) {
    (*audioPlayerObject)->Destroy(audioPlayerObject);
    audioPlayerObject = NULL;
}
 
// Output Mix解放
if (outputMixObject != NULL) {
    (*outputMixObject)->Destroy(outputMixObject);
    outputMixObject = NULL;
}
 
// エンジンの解放
if (engineObject != NULL) {
    (*engineObject)->Destroy(engineObject);
    engineObject = NULL;
    engineEngine = NULL;
}

以上でとりあえず音声再生までいけるはず(たぶん)。

エラー処理などがまともに入っていないのと、探り探り調べながら作ったため、そもそもの認識誤りなどあるかもしれないのでどうかご容赦を。

また、各APIの詳細なパラメータの説明まではちょっと書ききれないので追々。


【謝辞】 以下のサイトをかなり参考にさせて頂きました。この場を借りて御礼申し上げます。 http://d.hatena.ne.jp/miujun/20120307