カテゴリー
iOS Objective-C

CoreAudioを学ぶ(1)

AppleのCoreAudioを学ぶために資料を探していたところ、Learning Core Audioという本を見つけました。ただ、この本はObjective-Cで書かれていたり、少し古い本なのでコードがところどころDeprecatedになっているので困っていたところ、タイムリーにとてもいいブログを発見しました。Leaning Core Audioを読み解き、現在もコンパイルするコードを紹介してくれています。初学者がつまずきそうな箇所も丁寧に解説してくれているのがありがたいです!Objective-Cの勉強も兼ねて、このブログを読んで行こうと思います。ブログ著者さんの丁寧なティーチングスタイルがありがたかったので、僕も真似して出来るだけ端折らない記事を書いてみたいと思います。

まずは一番最初の記事、Reading Basic Info From a Local Audio Fileです。

#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>

void GetAudioFileInformationProperty(AudioFileID audioFile, CFDictionaryRef *dictionary) {
    OSStatus theErr = noErr;
    UInt32 dictionarySize = 0;
    theErr = AudioFileGetPropertyInfo(audioFile,
                                      kAudioFilePropertyInfoDictionary,
                                      &dictionarySize,
                                      0);
    assert(theErr == noErr);
    
    theErr = AudioFileGetProperty(audioFile,
                                  kAudioFilePropertyInfoDictionary,
                                  &dictionarySize,
                                  dictionary);
    assert(theErr == noErr);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        if (argc < 2) { // [1]
            printf("Usage: CAMetadata fullpath/to/audiofile\n");
            return -1;
        }
        
        // [2] 
        NSString *audioFilePath = [[NSString stringWithUTF8String:argv[1]] stringByExpandingTildeInPath];
        NSURL *audioURL = [NSURL fileURLWithPath:audioFilePath];
        
        // [3]
        AudioFileID audioFile;
        OSStatus theErr = noErr;
        
        // [4]
        // [5] 
        theErr = AudioFileOpenURL((__bridge CFURLRef)audioURL, kAudioFileReadPermission, 0, &audioFile);
        
        // [6] 
        assert(theErr == noErr);
        
        CFDictionaryRef dictionary;
        
        GetAudioFileInformationProperty(audioFile, &dictionary);
        
        NSLog(@"dictionary: %@", dictionary);
        
        CFRelease(dictionary);
        
        theErr = AudioFileClose(audioFile);
        
        assert(theErr == noErr);
    }
    return 0;
}

[1] argcとは実行ファイルを実行する時の引数の数です。Argument Countの略だと思います。C言語の実行ファイル実行時は [実行ファイル名] [引数1] [引数2] … という形でプログラムを呼びます(もちろん[ ]は省いてください)。実行ファイル名そのものもカウントされるので、引数が2つの場合argcは3です。argcが2よりも小さい場合、-1をリターンしてプログラムを終了させています。

[2]まず、Objective-Cスタイルのメソッド実行の書き方に面食らいますよね?Objective-Cではクラスからインスタンスを作る時、NSString *audioFilePathというふうに「ポインタ」で作成します。そして、メソッドの実行は[クラス名 クラスメソッド名:引数]と[ ]で囲むように書きます(クラスメソッドの場合)

NSString *audioFilePath = [[NSString stringWithUTF8String:argv[1]] stringByExpandingTildeInPath];

stringWithUTF8String メソッドはノン・ラテン文字もパースさせるためのもの。stringByExpandingTildeInPath はパスの中に「~」が存在する時にパースするためのものです。

Swiftに書き直すと、以下のようになると思います。(意訳です)

let audioFilePath: NSString = NSString.stringWithUTF8String(argv[1]).stringByExpandingTildeInPath()

[3] AudioFileID audioFile; とインスタンス作成をしています。なぜポインタで作成しないのか?AudioFileIDの定義を見てみると、typedef struct OpaqueAudioFileID *AudioFileID; と、構造体OpaqueAudioFileIDを*AudioFileIDというポインタでtypedefしているため、とわかりました。Swiftのコードを見ているとよくOpaquePointerとUnsafePointerというものが出てくるのですが、いまいち違いが分かっていませんでしたが、この記事を読むと少し違いが分かりました。C言語ではプログラムがheaderファイルとsourceファイルに分かれていますが、headerファイルに構造体の内容の記述がある場合はUnsafePointer、headerファイルには宣言のみで、sourceファイルに構造体の内容の記述がある場合はOpaquePointerとなるようです。

[4] __bridgeについても分からなかったので調べたところ、CoreFoundationのオブジェクトはARCの管理対象ではないため、それを管理対象とするためのキーワードのようです。

[5] CoreAudioではこのように、メソッドの引数にポインタのアドレス(&audioFile)を渡し、そこに値を代入する場合が多いように思います。

[6] assertは( )内の条件がtrueではない場合にログに吐き出すものです。

XcodeのCommand Line Toolの実行ファイルは、~/Library/Developer/Xcode/DerivedData/プロジェクト名/Products/Debugにありました。以下のようなコマンドで呼べます。

./CAMetadata ~/Desktop/sample.mp3