離散フーリエ変換 その1

MacとかiPhoneとかあまり関係なく基礎を固めようと思っていろいろ勉強モードに入っていまして、すこしずつまとめていこうと思います。まずはフーリエ変換についてです。

あくまでプログラムで使う事を前提に書いていきますので、いろいろおかしかったりするかもしれませんがご了承ください。もし、明らかに変だったら突っ込んでいただけるとうれしいです。基本的に小難しそうな数式とかはできるだけ出さずにコード重視、ビジュアル重視で書いていくつもりです。

プログラミングでフーリエ変換というと、高速フーリエ変換(FFT)を使うという事になると思うのですが、FFTについては特に詳しく書きません。プログラム化されたコードを見た場合、FFTだと高速化されたアルゴリズムだけで本質的な部分がわからないので、高速化していないノーマルな離散フーリエ変換(DFT)のプログラムを見て、フーリエ変換の仕組みを調べていきます。

オーディオ信号に対してDFTを使うと何ができるかというと、時間単位で並んでいる時間領域のオーディオデータの一部分を切り出して、含まれている周波数ごとの成分に分けた周波数領域のデータに変換する事ができます。逆に、その周波数領域のデータを時間領域のオーディオデータに戻す事もでき、それが逆離散フーリエ変換(IDFT)になります。

DFT01.jpg

離散フーリエ変換というのは、デジタルでサンプリングされたオーディオ信号のような飛び飛びのデータに対して行うフーリエ変換です。いきなり「離散」と出てくると何の事かわからないかもしれませんが、「離散」=「デジタル」と考えればよいと思います。アナログの「連続」に対して、デジタルの「離散」です。そのDFTを高速化したのがFFTという関係になります。

まずは何より使ってみない事にはしょうがないということで、サンプルソースです。

#include <stdio.h>
#include <math.h>
void DFT(int n, double *real, double *imag)
{
    double tmpReal[n], tmpImag[n];
    
    for (int i = 0; i < n; i++) {
        
        tmpReal[i] = 0.0;
        tmpImag[i] = 0.0;
        
        double d = 2.0 * M_PI * i / n;
        
        for (int j = 0; j < n; j++) {
            
            double phase = d * j;
            
            tmpReal[i] += real[j] * cos(phase);
            tmpImag[i] -= real[j] * sin(phase);
        }
    }
    
    for (int i = 0; i < n; i++) {
        real[i] = tmpReal[i];
        imag[i] = tmpImag[i];
    }
}
int main (int argc, const char * argv[]) {
    
    int n = 16;
    double real[n], imag[n];
    
    double d = 2.0 * M_PI / n;
    
    for (int i = 0; i < n; i++) {
        real[i] = sin(1.0 * i * d); //1Hzのサイン波
        real[i] += sin(3.0 * i * d + M_PI_4); //3Hzのサイン波(1/4πずらし)
        real[i] += sin(5.0 * i * d + M_PI_2); //5Hzのサイン波(1/2πずらし)
        imag[i] = 0.0;
    }
    
    //フーリエ変換
    DFT(n, real, imag);
    
    for (int i = 0; i < n; i++) {
        printf("%dHz %f\n", i, sqrt(real[i] * real[i] + imag[i] * imag[i]));
    }
    
    return 0;
}

Xcodeで実行する場合はCのCommand Line Toolでプロジェクトを作って、main.cを書き換えてください。

今回のコードでは、1Hzと3Hzと5Hzのサイン波をいろいろ位相を変えてミックスして元のデータを作ってから変換しています。結果は、

0Hz 0.000000
1Hz 8.000000
2Hz 0.000000
3Hz 8.000000
4Hz 0.000000
5Hz 8.000000
6Hz 0.000000
7Hz 0.000000
8Hz 0.000000
9Hz 0.000000
10Hz 0.000000
11Hz 8.000000
12Hz 0.000000
13Hz 8.000000
14Hz 0.000000
15Hz 8.000000

と表示されて、1Hzと3Hzと5Hzが含まれているということがわかります。上の周波数の11Hzと13Hzと15Hzにもデータが現れていますが、これは、ナイキスト周波数以上の周波数の音は、ナイキスト周波数を対称に折り返した低い周波数へ現れるということで出てきているものですので、単純に周波数特性を調べるだけなら無視してください(けっして無意味なデータというわけではありませんが...)。

コードの説明に入りまして、DFT関数がまさに離散フーリエ変換する関数になります。realに変換したいオーディオデータの配列、imagに同じサイズの空の配列、nに配列のサイズをそれぞれ渡すと、realとimagにフーリエ変換された値が返ってきます。

DFTをすると1つの周波数ごとにデータが実部と虚部という2つに分かれて返ってきます。今回のコードではrealが実部、imagが虚部のデータです。実部と虚部は2つでひとつの意味を持ったデータですので、どちらかだけを使うことはあまり無いと思います。main関数の中でやっていますが、周波数特性の振幅を得るという場合はrealとimagをそれぞれ2乗した値の平方根を求めます。

DFTして得られる周波数は、DFTに渡すサンプル数の長さを1Hzとして、0からサンプル数-1までの整数倍の周波数です。16サンプルの場合は、0・1・2・3・4・5・6・7・8・9・10・11・12・13・14・15Hzの16個です。ただこれが1
秒を1Hzと考えるときには、たとえば44.1kHzのオーディオの中の64サンプルを変換したという状況だと、0・689・1378...というふうにもっと広い間隔の周波数の並びになります。これはどこを基準にするかで違ってくるというだけの話ですので、このDFTの説明ページでは変換するサンプル数を1Hzとして表現します。

とりあえず、DFTができましたというところで1回目は終わりです。次へつづきます。

Touch the Wave 2 公開されました。

Touch the Waveの新バージョン「Touch the Wave 2」が公開されました。

諸事情のためTouch the Waveのアップデートではなく、新規アプリです。といっても有料になったわけではなく無料のままです。あくまで諸事情のためです。本当は前バージョンからのプレイリストの引き継ぎの機能を作ってあったのですが、新規アプリのため意味がなくなってしまいました。曲は再びダウンロードし直してください。

いちおうアプリ名の「2」はバージョン2ではなくて「2曲同時に再生できるよ」の「2」というつもりです。なので、「2」のバージョン1.0というよくわからないバージョン付けになっています。

変更点はいろいろありすぎるので全てリストアップするのはやめますが、一番大きな所は、

・2曲同時再生
・MP3やAACをダウンロード可能に
・ピッチの可変範囲が-100%〜+100%に設定可能

といったところでしょうか。

あと、パフォーマンス調整してiPod touchの第2世代以降だとフリックとかで音が途切れる事がなくなったとか、再生中の2曲にテンポが指定されていればスピードを合わせるのがボタンひとつだけで出来るとかってのもあります。まぁ、いろいろ使ってみてください。

appsページの方は作ってありますが、まだ細かい使い方までは書いてありません。余裕ができたらそのうち書きます。

iPhone Core Audio プログラミング

ちょっと記事にするには乗り遅れた感がありますが、「iPhone Core Audio プログラミング」という、iPhoneのオーディオプログラミングに特化した本が発売されています。今はもうだいたいどこの本屋さんいっても置いてありますね。詳しい内容については筆者の永野さんのサイトのページ「iPhone Core Audioプログラミング」(書籍) 発売へ。

iPhoneのオーディオのAPIについては、これ一冊に日本語でほぼ全て書いてあります。日本語です!iPhone Dev Centerにも「Core Audio 概要」とか「Audio Session Programming Guide」とかの日本語訳はありますが、Apple純正という安心感はあるものの翻訳なので、ネイティブな日本人が書いた文章というのはやっぱり分かりやすいです。もう僕がiPhoneの開発ネタを書く事もないかなぁなんて思ったり。

基本的にはAPIの解説になるので、本の最後の章にアプリのサンプルはいくつかありますが、実際に自分好みのオーディオアプリを作り始めてみると壁にぶち当たると思います(主にパフォーマンスで)。が、まあそれはトライアンドエラーで頑張りましょうってことで。作るアプリの内容とか機種とかによっても引っかかる部分が違いますし。なぜか新しい機種の方が負荷に弱かったりするときもあるんですよねぇ。

オーディオのプログラミングっていうと、オーディオの再生とか録音とかシンセサイズとかってところに興味がいってしまいがちだと思いますが、iPhoneに関していえば最も重要なのはAudio Sessionです!ちょっとでもiPhoneでオーディオ鳴らすなら、Chapter4と7は熟読してください!知っていてあえて使わないのと、何となくめんどくさいから読まないで知らずに使えないのとでは大きな差があります。まぁ、App Storeに出さずに自分のデバイスだけで動かせればいいっていうなら話は別ですけどね。他のChapterは気になるところだけピックアップして読めば良いと思います。

値段は4,200円と、他の入門書と比べてやや高いですが、それだけの価値はあると思います。僕はオーディオメインでプログラミングを勉強してますが、やっぱりその中でも興味があるところに偏って調べていたりするので、読んでみるといろいろ知らないところがたくさんあります。今後iPhoneのオーディオで、忘れていたり、何か新たに調べたくなったときに、役立ってくれる事は確実です。

Grand Central Dispatch その7 おまけ

さらに今更ながら、ASCII.jpさんの「マルチコア時代の新機軸! Snow LeopardのGCD」に、わりと一般向けの説明から始まって、コードを使用したプログラマ向けの説明までしているページがありました。とりあえずここらへんで基本知識を得てから、いろいろ試した方が良かったのかもしれません。

その記事を見ていて改めて発見したのは、dispatch_get_main_queueで、メインスレッドで実行されるキューが取得できるという事です。なるほど、これがあればperformSelectorOnMainThreadを呼ばなくてもディスパッチで呼び出しが完結できます。前回のタイマーとかはメインキュー使う方が良い気がします。

でも、こういった解説記事の中でディスパッチソースにまで触れられているものってあんまり見かけないですね。ディスパッチさえあればアプリの基本的な仕組みが作れてしまうみたいな。ということで、ソース系でもうひとつ調べてみました。

やってみたかったのは、ディスパッチでバッチ処理的な事をさせておいている途中で、キャンセルしたいって言う事です。すくなくとも僕の認識では、dispatch_asyncでブロックをキューに渡してしまうとキャンセルできない様なので、これをやりたいとおもって見つけたのがディスパッチソースの中のDISPATCH_SOURCE_TYPE_DATA_ADDというやつです。

それを使って書いてみたソースがこちらです。今回は自分でキャンセルしてみたいので、Cocoa Applicationでプロジェクトを作ってAppDelegateを以下のように変更し、Interface Builderでウィンドウにボタンをひとつ作ってAppDelegateのcancelにつなげてください。

//
// AppDelegate.h
//
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <nsapplicationDelegate> {
    NSWindow *window;
    dispatch_source_t _source;
}
@property (assign) IBOutlet NSWindow *window;
- (IBAction)cancel:(id)sender;
@end
//
// AppDelegate.m
//
#import "AppDelegate.h"
@implementation AppDelegate
@synthesize window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    dispatch_queue_t queue = dispatch_queue_create("test", NULL);
    _source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue);
    
    __block int count = 0;
    
    dispatch_source_set_event_handler(_source, ^{
        count++;
        printf("call %d\n", count);
        usleep(100000);
        dispatch_source_merge_data(_source, 1);
    });
    
    dispatch_source_set_cancel_handler(_source, ^{
        printf("end\n");
        dispatch_release(_source);
        _source = NULL;
        dispatch_release(queue);
        exit(0);
    });
    
    dispatch_source_merge_data(_source, 1);
    dispatch_resume(_source);
}
- (IBAction)cancel:(id)sender
{
    dispatch_source_cancel(_source);
}
@end

実行すると、event handlerのブロックが繰り返し呼ばれてログにカウントが表示されます。キャンセルボタンを押すと終了します。

DATA_ADDの使い方としては、dispatch_source_merge_data()の2つ目の引数で1以上の値を与えて呼ぶと、dispatch_source_set_event_handlerで登録したブロックが呼び出されるという感じになります。このサンプルでは、event_handlerのブロックの中から直接merge_dataを呼び出して処理を繰り返すようにしています。

merge_dataに0を渡してもブロックが呼ばれない事からも分かるように、たぶん本来はこれで処理自体をするものではなく、こちらのページにのっているように、dispatch_applyとかでやっている処理の進捗の量をmerge_dataの引数で受け取ってインジケータに表示するような用途に使うものだと思われます。

Grand Central Dispatch その6 timer

いまさらながら、GCDのサンプルソースはMac Dev Centerにもいろいろありますので参考に。中でもDispatch_Samplesなんかはシンプルなサンプルがたくさん集まってますので、最初に見るには良いと思います。

今回からはディスパッチの中でもソースというものを見ていこうと思います。前回のエントリにキューとソースは別なんて一度書いてしまいましたが、間違いでソースでもキューは使います。

と書いてて、ディスパッチのソースと、ソースコードの区別がわけ分からなくなりそうなので、ディスパッチのソースは「ディスパッチソース」と呼ぶ事にします。

ディスパッチソースの種類にはいろいろありますが、基本的には、何かしら外部からイベントを受け取ったらキューにブロックを渡すというもののようです。以下のようなものがあります。

DISPATCH_SOURCE_TYPE_DATA_ADD:
DISPATCH_SOURCE_TYPE_DATA_OR:
DISPATCH_SOURCE_TYPE_MACH_SEND
DISPATCH_SOURCE_TYPE_MACH_RECV
DISPATCH_SOURCE_TYPE_PROC
DISPATCH_SOURCE_TYPE_READ
DISPATCH_SOURCE_TYPE_SIGNAL
DISPATCH_SOURCE_TYPE_TIMER
DISPATCH_SOURCE_TYPE_VNODE
DISPATCH_SOURCE_TYPE_WRITE

まあ、ほとんどは自分じゃ使わないなぁと思って調べてなかったりするんで、簡単なそうなタイマーを見ていきます。まずはサンプルソースから。

#include <stdio.h>
#include <stdlib.h>
#include <dispatch/dispatch.h>
int main (int argc, const char * argv[]) {
    dispatch_queue_t queue = dispatch_queue_create("timerQueue", 0);
    //タイマーのソースを作成
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    __block int i = 0;
    //イベントを受け取った時に実行されるブロックをソースに設定する
    dispatch_source_set_event_handler(timer, ^{
        printf("%d\n", i);
        i++;
        if (i == 10) dispatch_source_cancel(timer);
    });
    //ソースがキャンセルされてときに実行されるブロックをソースに設定する
    dispatch_source_set_cancel_handler(timer, ^{
        dispatch_release(timer);
        dispatch_release(queue);
        printf("end\n");
        exit(0);
    });
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC); //今から1秒後
    uint64_t interval = NSEC_PER_SEC / 5; //0.2秒
    //タイマーを設定する
    dispatch_source_set_timer(timer, start, interval, 0);
    printf("start\n");
    dispatch_resume(timer);
    dispatch_main();
    return 0;
}

実行すると、ログに0から10まで数字が0.2秒おきに出力されて、アプリが終了します。

では、コードを上から見ていきます。まずは、タイマーのディスパッチソースを作成しています。ディスパッチソースの作成はdispatch_source_createです。

dispatch_source_t
dispatch_source_create(dispatch_source_type_t type,
uintptr_t handle,
unsigned long mask,
dispatch_queue_t queue);

ひとつめの引数にはディスパッチソースの種類を渡します。タイマーはDISPATCH_SOURCE_TYPE_TIMERです。2つめの引数のhandleは、ディスパッチソースが動作する元になるものを渡しますが、タイマーは必要ないので0で良いです。3つ目もタイマーでは必要ありませんので、これも0です。最後のqueueには、ディスパッチソースからブロックを渡すキューを設定します。

ちなみに、キューはグローバルでもシリアルでも使えますが、グローバルキューだと他の色んなブロックが入っていたら後回しになって影響を受けて実行されるのが遅れやすくなりそうなので、シリアルキューにして完全にセパレートされている方が良いのかなぁと思います。あくまで予測ですけど。

dispatch_source_set_event_handlerは、イベントを受け取ったときに呼ばれるブロックを設定します。今回はタイマーですので、タイマーが定期的にイベントを投げてきて、ここのブロックが呼ばれる事になります。

dispatch_source_set_cancel_handlerはディスパッチソースがキャンセルされたときに呼ばれるブロックを設定します。基本、タイマーのディスパッチソースやキューを解放します。

dispatch_source_set_timerでは、タイマーの開始時間とインターバルを指定します。スタートの時間は、dispatch_timeを使うと今から何秒後みたいな感じで絶対時間のdispatch_time_tが取得できます。インターバルは、1秒がNSEC_PER_SECで定義されていますから、そこから求めるのが良いと思います。

で、忘れていけないのは、dispatch_resumeです。ディスパッチソースは作った段階ではsuspend状態ですので、resumeで発動させなければいけません。

Grand Central Dispatch その5 semaphore

ディスパッチはいままでの並列処理の方法と比べるとシンプルで簡単だ、なんて書かれていたりしますけど、どうやらセマフォは欠かせないようで、ディスパッチにもあります。

とりあえずサンプルコードから。

#include <stdio.h>
#include <unistd.h>
#include <dispatch/dispatch.h>
int main (int argc, const char * argv[]) {
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    
    for (int i = 0; i < 10; i++) {
        dispatch_group_async(group, queue, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            //クリティカルセクション開始
            printf("start %d\n", (int)i);
            usleep(10000);
            printf("end %d\n", (int)i);
            //クリティカルセクション終了
            dispatch_semaphore_signal(semaphore);
        });
    }
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    dispatch_release(semaphore);
    dispatch_release(group);
    
    return 0;
}

ディスパッチのセマフォの使い方としてはまず、dispatch_semaphore_createでセマフォのオブジェクトdispatch_semaphore_tを作ります。引数にはセマフォの旗の数を指定できます。0だと一回も進入できなくなってしまいますので1以上を指定します。

そして、クリティカルセクションをdispatch_semaphore_wait〜dispatch_semaphore_signalで囲みます。それぞれ引数にセマフォオブジェクトを渡します。waitでは進入できない時に待つ時間を指定できて、DISPATCH_TIME_FOREVERなら永久に待ちます。

サンプルコードを実行すると、クリティカルセクション開始〜終了としているところは同時に実行されず、printfでstartとendがセットで0から9まで順番に出力されます。もし、セマフォ関連の関数を排除したら、startのあとにスリープさせていますので、startが10個出力された後に、endが10個出力されるはずです。

という感じで、ディスパッチのキューに直接ブロックを渡す関係は以上で終わりです。ちなみに、キューにブロックを渡してしまうと後からキャンセルが出来なかったりしますのでご注意を。

その処理のキャンセルとかも含めて、ディスパッチにはソースという仕組みも用意されていますので、次回以降見ていこうと思います。

CoreAudio その3〜5改 AudioObjectでのプロパティ取得・設定

Mac OS X 10.6 Snow Leopardから、AudioHardware〜とかAudioDevice〜とかAudioStream〜とかのGet・Set系がことごとくDEPRECATEDになってしまいました。

僕の認識では、もともとAudioObject〜があって、そのコンビニエンスメソッドとしてAudioDeviceとかの関数があると思っていたのですけど、どうやら歴史的には逆だったようです。ヘッダをよく見てみたら、AudioObject系はわりと最近の10.4から増えてたんですね。

あと、AudioUnitがiPhoneと同じようにAudioComponentにいきなり変わっててびっくりしました。iPhoneで慣れ親しんでいたとはいえ、もちょっと緩やかに移行してくれてもいいんじゃないかなと思います。

プロパティの定数系も結構Deprecatedしてます。これはXcodeで警告を出してくれないので注意が必要です。ヘッダのコメントに”Some Day Be Deprecated”なんてこっそりかかれてます。Device系だとkAudioDevicePropertyDeviceNameから始まるところですね。10.7になっていきなりエラー出まくりなんて事のないようにしっかりチェックしておきましょう。

ということで、取得・設定系をAudioObjectを使ってやってみたいと思います。

AudioObjectでオーディオ情報の取得・設定をするにはAudioObjectGetPropertyDataやAudioObjectSetPropertyDataという関数を使います。これはいままでAudioHardware〜やAudioDevice〜など別々の関数でやっていたオーディオ情報の取得や設定を、ひとつでまかなえるものです。プロパティのサイズを調べるのにも、AudioObjectGetPropertyDataSizeという関数で行う事になります。

たとえば、プロパティの取得する関数はこんな感じで定義されています。

extern OSStatus
AudioObjectGetPropertyData(AudioObjectID inObjectID,
                           const AudioObjectPropertyAddress* inAddress,
                           UInt32 inQualifierDataSize,
                           const void* inQualifierData,
                           UInt32* ioDataSize,
                           void* outData)

最初のAudioObjectIDというのはAudioDeviceIDやAudioStreamIDをそのまま渡します。前の記事のその3でも説明しましたが、AudioDeviceIDもAudioStreamIDもAudioObjectIDをtypedefしているだけのもので、つまり全てAudioObjectIDです(リファレンス的にAudioDeviceなどはAudioObjectのサブクラスってことらしい)。AudioHardware〜系の関数をつかっていたプロパティのときはkAudioObjectSystemObjectを渡します。

ちょっとひとつ飛ばしまして、inQualifierDataSizeとinQualifierDataは、サンプルで使っているものが無かったのでよくわかりません。基本0とNULLで大丈夫なようです。もしかしたらまだ機能していないかもしれません。ioDataSizeとoutDataは取得するプロパティのサイズと受け取るメモリ領域で、前と同じです。

戻ってAudioObjectPropertyAddressは、

struct  AudioObjectPropertyAddress
{
    AudioObjectPropertySelector mSelector;
    AudioObjectPropertyScope    mScope;
    AudioObjectPropertyElement  mElement;
};
typedef struct AudioObjectPropertyAddress   AudioObjectPropertyAddress;

と定義されてます。デバイスとかストリームとか関係なくプロパティを特定するのに必要な情報ですね。

Selectorは〜PropertyIDの事です。Scopeは、ほとんどの場合はkAudioObjectPropertyScopeGlobalで、Deviceなどの入出力部分のときはInputとかOutputで指定したりします。Elementは基本kAudioObjectPropertyElementMaster(= 0)でOKだと思います(これ以外指定するパターンがすぐに見つけられませんでした。Channel的なところで0以外を使うのではないかと思います)。

最後に、サンプルソースです。Xcodeで新規プロジェクト→Command Line Tool→Foundationでプロジェクトを作成して、CoreAudio.Frameworkを追加して、main.mを以下のソースに差し替えてください。走らせると、デフォルトになっているオーディオデバイスの名前を表示して、サンプルレートを設定可能な中で変更します。

#import <Foundation/Foundation.h>
#import <CoreAudio/CoreAudio.h>
int main (int argc, const char * argv[]) {
    
    AudioDeviceID devID;
    UInt32 size;
    AudioObjectPropertyAddress address;
    AudioValueRange *sampleRates;
    CFStringRef deviceName = NULL;
    //デフォルトのアウトプットに設定されているオーディオデバイスを取得する
    address.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
    address.mScope = kAudioObjectPropertyScopeGlobal;
    address.mElement = kAudioObjectPropertyElementMaster;
    size = sizeof(devID);
    
    AudioObjectGetPropertyData(kAudioObjectSystemObject, &address, 0, NULL, &size, &devID);
    
    //アウトプットの名前を取得する
    address.mSelector = kAudioObjectPropertyName;
    size = sizeof(deviceName);
    
    AudioObjectGetPropertyData(devID, &address, 0, NULL, &size, &deviceName);
    
    //アウトプットのデバイスが対応しているサンプルレートを取得する
    address.mSelector = kAudioDevicePropertyAvailableNominalSampleRates;
    AudioObjectGetPropertyDataSize(devID, &address, 0, NULL, &size);
    
    UInt32 numOfSampleRates = size / sizeof(AudioValueRange);
    sampleRates = calloc(numOfSampleRates, sizeof(AudioValueRange));
    
    AudioObjectGetPropertyData(devID, &address, 0, NULL, &size, sampleRates);
    
    //現在のサンプルレートを取得する
    Float64 currentSampleRate;
    address.mSelector = kAudioDevicePropertyNominalSampleRate;
    size = sizeof(Float64);
    
    AudioObjectGetPropertyData(devID, &address, 0, NULL, &size, ¤tSampleRate);
    
    //サンプルレートを別のにする
    int currentIndex = 0;
    for (int i = 0; i < numOfSampleRates; i++) {
        if (sampleRates[i].mMinimum == currentSampleRate) {
            currentIndex = i;
            break;
        }
    }
    
    int newIndex = currentIndex + 1;
    if (newIndex >= numOfSampleRates) newIndex = 0;
    
    //サンプルレートを設定する
    Float64 newSampleRate = sampleRates[newIndex].mMinimum;
    size = sizeof(Float64);
    
    AudioObjectSetPropertyData(devID, &address, 0, NULL, size, &newSampleRate);
    
    NSLog(@"device name = %@", deviceName);
    NSLog(@"new samplerate = %f", newSampleRate);
    
    free(sampleRates);
    CFRelease(deviceName);
    
    return 0;
}

Grand Central Dispatch その4 group

前回紹介したdispatch_applyはブロックの処理が終わるまで待ってくれていましたが、dispatch_asyncは別スレッドで処理されてしまうので、どの順番で処理されるのかも分からない状態でブロックを処理させっぱなしになってしまいます。

グループというのを使えば、asyncで処理する複数のブロックをひとつのグループにまとめて、それらのブロック全部が終わったタイミングを受け取る事が出来ます。

まず、グループを作る関数がこれです。

dispatch_group_t dispatch_group_create(void);

次に、グループで処理させたいブロックを渡すのがdispatch_group_asyncです。グループはasyncオンリーでsyncはありません。

void
dispatch_group_async(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);

そして、グループの処理全部が終わったら呼ばれるブロックを設定するのがdispatch_group_notifyです。基本的に、グループ内共通で使ったメモリ領域などを解放するのに使うようです。どこかに通知するのにも使えると思います。

void
dispatch_group_notify(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);

カレントスレッドで処理の終わりを待つ必要があれば、待たせることもできます。dispatch_group_waitです。どれだけ待つのかを指定できます。DISPATCH_TIME_FOREVERという定数をtimeoutに渡せば、いつまでも待ちます。

long
dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);

なお、waitで待っても待たなくても、notifyで設定したブロックが呼ばれるのは別スレッドのようです。

サンプルソースです。

#include <stdio.h>
#include <dispatch/dispatch.h>
int main (int argc, const char * argv[]) {
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    __block int sum = 0;
    
    for (int i = 0; i < 10; i++) {
        dispatch_group_async(group, queue, ^{
            printf("+%d\n", i);
            sum += i;
        });
    }
    
    dispatch_group_notify(group, queue, ^{
        printf("finish");
        //<strike>dispatch_release(group);</strike>
    });
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    printf("sum = %d\n", sum);
    dispatch_release(group);
    
    return 0;
}

前回のapplyと同じような処理をさせています。waitが無ければ、おそらくsumは0で終わってしまうと思いますが、waitで待っている事でちゃんと45と答えが出ます。

waitの代わりにdispatch_main()にしておいてnotifyのブロックの中でsumを見れば、そこでも45となっていて、ちゃんとasyncで渡したブロックが全て実行された後に呼ばれる事が分かると思います。

ちなみに、notifyのブロックが呼ばれる前にasyncで新たなブロックを同じグループに入れると、その新しいブロックも終わってからnotifyのブロックが呼ばれます。が、、notifyのタイミングが新たなブロックの前か後かどちらでくるか分からなくなってしまうので、やらない方が良いと思います。

Grand Central Dispatch その3 apply

前回のコードのように同じ処理をforループでキューに渡すようなとき、それ専用の関数がディスパッチには既に用意されています。dispatch_applyです。

void
dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));

iterationsの数だけブロックをキューに渡して処理させます。ブロックはsize_tの引数を一つだけ持ち、よくあるforループと同じように0から1ずつ増やした値が渡されます。

まずは、サンプルコードです。

#include <stdio.h>
#include <dispatch/dispatch.h>
int main (int argc, const char * argv[]) {
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    __block int sum = 0;
    
    dispatch_apply(10, queue, ^(size_t i){
        sum += i;
    });
    
    printf("sum = %d\n", sum); // sumは45になる
    
    return 0;
}

これを実行すると、0から9までがsumに足されて45になります。

dispatch_applyはグローバルキューに渡していても、ちゃんと全てのブロックが処理されるのを待ちますので、dispatch_main()が無くてもアプリが終了する前に全て足された答えが出ます。ですが、sync的に順番に処理されている訳ではなく、並列に処理はされています。ただ、asyncと同じという訳でもなく、applyでは、呼び出したスレッドと別スレッドを組み合わせて並列にブロックが実行されているようです(asyncは別スレッドだけ)。

ブロックの途中でスリープさせたりとかして試してみると、applyでは同時に2つまでしか処理されません(試したのがMacBook Airでコアが2つだから?)。asyncだといくらでも同時に実行されてしまいます。このあたりは、使い道の違いって感じですかね。

Grand Central Dispatch その2 Queue

Queueを作る

ディスパッチの基本的な使い方は、処理したいコードをブロックや関数で用意しておいて、キューにそのブロックや関数を渡すという流れになります。

ディスパッチのキューには2種類あります。アプリ(OS?)に最初から一つ用意されているグローバルキューと、自分で作るキューです。グローバルキューは並列に処理できますが、自分で作るキュー(以後シリアルキューと書いて区別します)は入れていった順番に直列でしか処理できません。

キューはdispatch_queue_tという型のオブジェクトで表されます。ディスパッチのオブジェクトは他にもいくつかありますが、それは次回以降に後回しにします。

グローバルキューを取得するにはこの関数を使います。

dispatch_queue_t
dispatch_get_global_queue(long priority, unsigned long flags);

一つ目の引数にはキューが処理される優先度を指定します。普通はDISPATCH_QUEUE_PRIORITY_DEFAULTを指定すれば良いと思います。DEFAULT以外にもHIGHとLOWがあります。HIGHの処理が全て終わってからDEFAULT、DEFAULTが全て終わってからLOWという順番で処理されていくようです。2つ目の引数はヘッダを見るとReserved for future useとなっていて今は何も意味しないので、0を入れておけば良いです。

シリアルキューはこの関数で作ります。

dispatch_queue_t
dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

一つ目の引数はキューに付けられるラベルで、サンプルを見る限り逆DNS記法が推奨されているようです。でも、同じラベルでふたつキューを作っても、同じキューが返されるという事は無く、別のキューが作られるようです。2つ目の引数はこれまたヘッダにUnusedなんて書かれていて、こちらはNULLでも入れておけば良いようです。

なお、Carbonに慣れ親しんでいる方はお分かりかと思いますが、関数名にcreateとついているのでdispatch_queue_createで作ったキューは使い終わったら解放しなければいけません。それを行うのはdispatch_releaseです。

dispatch_release(queue);

使うかどうか分かりませんが、リテインカウントをあげる事も出来ます。

dispatch_retain(queue);

これらretainとreleaseはキュー以外のオブジェクトにも使います。

Queueに処理を渡す

キューを取得できたら、ブロックを渡して処理をさせます。キューを使って処理を開始させる関数はいろいろありますが、まず基本的な2つを見てみます。こんなかんじで定義されています。

void
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
void
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

ayncは非同期でバラバラに、syncは同期で順番に、キューにブロックを処理をさせようとする関数です。と説明すると、キューにも同じような用途で2種類あるのに何で関数がまた同じように2種類分かれているんだと思ってしまうのですが、ちょっと役割が違います。

ブロックが処理されるスレッドを調べてみると、asyncは呼び出し元のスレッドとは別のスレッドでブロックを処理させ、syncは呼び出し元と同じスレッドで処理をさせるようです。

実際の挙動としては、グローバルキューにasyncでブロックを渡すと別スレッドで並列に実行されますが、syncで渡すと現在のスレッドをつかうのでそのまま直列に実行されます。シリアルキューにsyncで渡せばもちろんそのまま直列に実行ですが、asyncで渡すと入れていった順番に別スレッドで実行されます。

グローバルキュー+syncの組み合わせはありえないような気がするんですが、もしかしたら想像もつかない用途があるかもしれません。他にもディスパッチ用の関数はたくさんあるんで意味が無さそうな組み合わせはいろいろ出てくるかもしれません。

それと、ここでつかえるブロックは返り値も引数もなしのブロックです。

実行してみる

実際にディスパッチを使ってみます。ディスパッチを使うには<dispatch/dispatch.h>をincludeしないといけないのですが、Objective-Cで使う場合はFoundationに既に含まれているので何も気にしなくても使えます。

以下、サンプルのソースコードです。単純にディスパッチを試すためC言語だけで組んであります。新規プロジェクトをCommand Line ToolのTypeをCで作成して、main.cを以下のコードに差し替えてください。あっ、もちろんSnow LeopardのXcode3.2以降で実行してください。

#include <stdio.h>
#include <stdlib.h>
#include <dispatch/dispatch.h>
int main (int argc, const char * argv[]) {
    
    dispatch_queue_t gQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(gQueue, ^{
        
        dispatch_queue_t sQueue = dispatch_queue_create("jp.objective-audio.sample1", NULL);
        
        for (int i = 0; i < 10; i++) {
            dispatch_sync(sQueue, ^{
                printf("block %d\n", i);
            });
        }
        
        dispatch_release(sQueue);
        
        printf("finish\n");
        exit(0);
    });
    
    dispatch_main();
    
    return 0;
}

dispatch_asyncで非同期で実行するブロックの中でシリアルキューを作って、10回ほどdispatch_syncでブロックを実行させています。その処理が終わったら”finish”と表示してアプリ終了です。

ブロックの記述は、わざわざ宣言とかしなくても、こんなかんじで関数の中に直接書けちゃいます。

最後のdispatch_main()というのはメインスレッドを休止させるもので、これを使わないとdispatch_asyncでグローバルキューにブロックを実行させる前にアプリがreturn 0で終了してしまいます。NSApplicationMain()かCFRunLoopRun()が走っている場合には必要ありません、というかdispatch_main()を呼び出すと固まります。

キューの種類を入れ替えてみたり、syncとasyncを入れ替えてみたりすると、どんな感じで動作するか分かるかと思います。asyncのところをsyncにするとdispatch_main()が無くてもちゃんと全部実行されますし。逆にsyncのところをasyncにすると、ブロックの処理が別スレッドに行ってしまうので、実行される前に”finish”となります。

とりあえず今回は以上です。asyncやsync以外にもディスパッチでブロックを実行させる関数があるので、また次回見ていきます。