読者です 読者をやめる 読者になる 読者になる

すこしふしぎ.

VR/HI系院生による技術ブログ.まったりいきましょ.(友人ズとブログリレー中.さぼったら焼肉おごらなきゃいけない)

openAL入門 - 1.音を鳴らそう!

openAL?

最近,iOS上でサウンドを鳴らそうとごにょごにょしています. ジャイロセンサの値をもとに3次元音響的なことをやろうとしています.

iOSにおいて音をならそうとすると,次のような選択肢があるようです.

  • AVAudioPlayer
  • core audio
  • openAL

などなど.他にもサードパーティ製のライブラリとかもあるのかな.調べてないけど.

で,例えばボタン効果音とかを鳴らすだけ,というシンプルな目的であればAVAudioPlayerでさくっと鳴らすのが適していると思います. 処理がシンプルで,既存プロジェクトをあまり汚さず音を鳴らすことができます.

一方,複雑な信号処理を求められる場合(エコーフィルタとか,ボーカルリデューサーとか)はcore audioというレイヤーまでおりていく必要がありそうです. しかし今回は自力でHRTF畳み込みのような本気の3次元音響ではなく,自分の位置に合わせて音の左右パンが変更される程度のなんちゃって3D音響ができればokです.それでわざわざcore audio叩くのもなぁ,という感じで出会ったのがopenALです.

openALクロスプラットフォームで使える,3次元音響のためのライブラリです. それこそHRTF畳み込みとかも自動でやってくれる感じです. 実装としてもAPIの構成は意識的にopenGLに似せてあるそうで,そちらに慣れている人であれば割と直感的に使えるようです. 確かにちょろっと見てみた感じだとbufferに読み込んでindexで参照して..みたいな流れはopenGLのtextureとかFBOの扱いに似ている印象でした.

今回は,openALを使って音を鳴らすまでの簡単なチュートリアルを行います. iOS dev centerから"oalTouch"というサンプルコードをDLしておくとよいです. 公式のopenALサンプルはこれだけなので. (しかしiOSに慣れていない身としてはなかなか読みづらいです..w

openALで音ならすコード

framework準備

まずopenAL使うためには

  • AudioToolBox.framework
  • OpenAL.framework

をプロジェクトに追加する必要があります. こんな感じで.

f:id:ism1000ch:20131213173537p:plain

f:id:ism1000ch:20131213173545p:plain

上記2つを追加すればOpenALの関数が使えるようになります.やったね.

openALのコード書き方

iOSに限らず,openALの使い方はC++等でも共通なので,一回覚えてしまえば応用きくかもしれません.

まずはheader.

//
//  openALtest
//

#import <UIKit/UIKit.h>
#import <OpenAL/al.h>
#import <OpenAL/alc.h>
#import <AudioToolbox/AudioToolbox.h>
#import "MyOpenALSupport.h"

@interface ismViewController : UIViewController{
    ALuint buffer;
    ALuint source;
}
@end

openAL関連とAudioToolbox,そしてoalTouchからmyOpenAlSupportというユーティリティ関数群をimportしておきます. 音データを読み込むときに便利になります.

あとは変数としてbuffer番号とsource番号の入る変数を用意しておけばokです.

そして実装のほう. openALで音を鳴らすためには,以下の用語を知っておくとよいです.

buffer

読み込んだ音声データを保存しておくメモリ領域.波形情報が入ってるイメージ.

source

仮想空間中に置く音源の様々な情報を格納しておく.空間中の位置情報,再生情報,音量など様々な要素はsourceに対するプロパティで設定する.sourceに読み込んだbufferを対応させることで,音データが仮想空間中に配置される.

listener

仮想空間中のユーザの情報を格納しておく.空間中のユーザ位置情報,移動情報,向き情報などのプロパティを設定する.

そして実際に音を鳴らすのは,以下のステップになります.

  1. openALのコンテクスト準備
  2. 曲データ読み込むbuffer用意
  3. 空間に配置するsource用意
  4. sourceのプロパティ設定
  5. caf(音データ)をbufferに読み込み
  6. listerの設定
  7. 音楽再生

いかがでしょうか.ぱっと見ただけでも結構なステップですね... 各ステップにコメント加えつつの実装ファイルが以下.

//
//  openALtest
//

#import "ismViewController.h"

@interface ismViewController ()

@end

@implementation ismViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    // 1.openALコンテクストの用意
    ALCdevice *device = alcOpenDevice(NULL); //一番始めのデバイスを使います
    
    if (device)
    {
        ALCcontext *context = alcCreateContext(device, NULL); //コンテクストの作成
        alcMakeContextCurrent(context); //作成したコンテクストをカレントに設定?
    }
    
    // 2.buffer用意
    ALenum error;

    alGetError(); //Gen前に呼んでおく.errorをクリア.
    alGenBuffers(1, &buffer); //曲データ1つにつきバッファ1つ.

    if ((error = alGetError()) != AL_NO_ERROR)
    {
        // エラー処理
        NSLog(@"alGenBuffers error");
        return;
    }
    
    // 3.source用意
    
    alGetError(); // gen前.errorをクリア.
    alGenSources(1, &source); //空間に配置する数の分生成する.
    
    if ((error = alGetError()) != AL_NO_ERROR)
    {
        // エラー処理
        NSLog(@"alGenSources error");
        return;
    }
    
    // 4.sourceのプロパティ設定
    alSourcei( source, AL_LOOPING , AL_TRUE);   // 繰り返し
    alSourcei( source, AL_PITCH   , 1.0f);      // ピッチ
    alSourcei( source, AL_GAIN    , 0.45f);     // 音量
    alSource3f(source, AL_POSITION, 10, 20, 30); // 音源位置

    // 5.caf読み込み
    void    *data;
    ALenum  format;     // フォーマット
    ALsizei size;       // ファイルサイズ
    ALsizei freq;       // 周波数
    NSBundle *bundle = [NSBundle mainBundle]; //iPhoneのdir読み込みのため.
    NSString *fileName = @"dir";

    // bufferによみこみ
    CFURLRef fileURL = (__bridge CFURLRef)[NSURL fileURLWithPath:[bundle pathForResource:fileName
                                                                                  ofType:@"caf"]];
    data = MyGetOpenALAudioData(fileURL, &size, &format, &freq);  // sample内の読み込み関数を拾ってきた.
    alBufferDataStaticProc(buffer, format, data, size, freq); // bufferにデータを登録

    // source と buffer の接続
    alSourcei(source, AL_BUFFER, buffer);
    
    // 6.listerの設定
    ALfloat listenerPos[] = {0.0, 0.0, 0.0};
    ALfloat listenerVel[] = {0.0, 0.0, 0.0};
    ALfloat listenerOri[] = {0.0, 0.0, -1.0, 0.0, 1.0, 0.0}; //視線ベクトル.姿勢ベクトル

    alListenerfv(AL_POSITION, listenerPos);
    alListenerfv(AL_VELOCITY, listenerVel);
    alListenerfv(AL_ORIENTATION, listenerOri);
    
    // 7.再生
    alSourcePlay(source);
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
@end

公式リファレンスを見ると,alGenBuffers,alGenSourcesの前にはalGetError()関数を呼べと書いてありました. エラー処理のためみたいです. alGetError()を呼ぶとエラー状況を取得し,内部状態としてはエラー情報がクリアされるようです.

とまぁこんな感じでopenALを使ったアプリを作れるようになります. openALのすごいところは一度sourceとlistenerを仮想空間に配置してしまえば,以降はその位置を変更するだけで自動的に立体音響として提示してくれるところです.

例えばボタンを押されたらlistenerがどの方向を向く,とかジャイロセンサの値を使って方向変更とかも結構簡単です. 今回のサンプルはシンプルな音再生ですが,インタラクティブなサウンドコンテンツを作るとっかかりとしてはopenAL,おすすめですよ.