Microsoft WAVE ファイルを Core Audio で読み込む

Last modified Fri Mar 5 2004

間違いを発見した方、ぜひお知らせください。(_ _)


Cocoa 自体には音を扱う方法はほとんど用意されていない。一応 NSSound というクラスに音ファイルを読み込むことができるが、それらは単にオーディオデバイスから出力するためのもので、音声波形そのものにアクセスすることはできない。したがって、WAVE ファイルを読み込んで何らかの信号処理をする、あるいはファイルに書き出すなどの処理をしたい場合には、NSSound 以外の方法が必要となる。

WAVE ファイルを読み込むというタスクは単純かつ基本的だが、Cocoa でそれをする方法についての解説はほとんどない。上に書いたように、Cocoa 自体にその機能がないからである。こんな単純なタスクでも、WAVE 形式の仕様書を片手に1からガリガリとコーディングするのは結構面倒くさいし、応用範囲が狭くなりすぎる。やはり、ちゃんとした API を利用したくなるのが人情というものである。

Cocoa アプリ上でこのタスクを実現するには、

などの方法が考えられる。

libsndfile はある意味最も簡単かつ確実であろう。これで満足してしまえる人は、もうこれ以降を読む必要はない。しかしながら、UNIX 流の解決法でなく Mac OS X ならではの先進的な機能を亨受したいという人は、その先に楽園が待っていることを信じ、敢えてイバラの道を選ぶかもしれない。

新しく Cocoa アプリケーションを開発したいと願う人の中に、敢えて Sound Manager を使いたいと願う酔狂人はいまい。このあまりにもレガシーな API のドキュメントからは、切れたリンクしか見つからない。既に Classic/Carbon 用の音ルーチンの十分な開発実績がある人以外には、とてもすすめられない。そもそも、Sound Manager には AIFF ファイルを読み込む機能はあるが、WAVE ファイルを読み込めるものかどうか・・・。

音を扱う機能はほんの一部分に過ぎないが、QuickTime API は1つの解ではある。QuickTime 自体はレガシーではない。しかし、その長い歴史を感じさせる膨大な資料は、未経験者には敷居が高すぎる。(興味はあるけどね)

結局のところ、Cocoa では Core Audio が唯一まともな音 API ということになる。Core Audio は Mac OS X のために開発された。何となく、Mac OS X の音機能をフルに利用できそうな予感がする。

前置きが長くなったが、この文書は、Objective-C + Cocoa でアプリケーションを開発することを前提として、その中に WAVE ファイルを読み込むというタスクを Core Audio で実現するやり方を示すものである。Core Audio 自体も機能が盛りだくさんで、必要十分な情報にアクセスするのはなかなか難しい。目的をしぼったレシピは、きっと後で何かの役に立つだろう。

手順

1. ヘッダを読み込む

#import <AudioToolbox/AudioFile.h>
#import <AudioToolbox/AudioConverter.h>

Audio Toolbox は Core Audio を構成するフレームワークのひとつ。

2. FSRef を用意

当然ながら、まず WAVE ファイルを開く必要がある。fopen(3) なんかの場合にはファイル名を char * 型のポインタで与えるわけだが、Core Audio では FSRef という型を使う。文字列から FSRef に変換するには、次のやり方がひとまず無難そう。(日本語のファイル名も大丈夫だった)

    FSRef ref;
    NSString *path=@"/path/to/hoe.wav";
    // FSRefを用意
    if(FSPathMakeRef([path fileSystemRepresentation], &ref, NULL)) {
        NSLog(@"FSPathMakeRef failed");
        return nil;
    }

FSPathMakeRef は実は Carbon の関数だが、別に気にしなくてよい。

3. ファイルを開く

    AudioFileID mAudioFileID;
    // ファイルを開く
    if(AudioFileOpen(&ref, fsRdPerm, 0, &mAudioFileID)) {
        NSLog(@"AudioFileOpen failed");
        return nil;
    }

4. 音ファイルの基本情報を取得

ヘッダ情報のようなもの。まず、基本情報全体を AudioStreamBasicDescription 型の構造体にストア。

    AudioStreamBasicDescription mFileDescription;
    // 読みこんだファイルの基本情報を mFileDescription へ
    UInt32 propertySize = sizeof(AudioStreamBasicDescription);
    if(AudioFileGetProperty(mAudioFileID, kAudioFilePropertyDataFormat, 
                            &propertySize, &mFileDescription)) {
        NSLog(@"AudioFileGetProperty failed");
        AudioFileClose(mAudioFileID);
        return nil;
    }

このデータはストリーム指向なので、データ長の情報がない。データ長は別に読み込んでおく必要がある。ここで読み込むデータ長は64ビット整数、後で使うデータ長は32ビット符号無し整数と微妙に違うので要注意。

    SInt64 dataSize64;
    UInt32 dataSize;
    // 読みこんだファイルのデータ部のバイト数を dataSize へ
    propertySize = sizeof (SInt64);
    if(AudioFileGetProperty(mAudioFileID, kAudioFilePropertyAudioDataByteCount, 
                            &propertySize, &dataSize64)) {
        NSLog(@"AudioFileGetProperty failed");
        AudioFileClose(mAudioFileID);
        return nil;
    }
    dataSize=(UInt32)dataSize64;

5. ファイルから読み込み

いよいよ読み込み。感覚的には、fread と同じようなものだが WAVE ファイルのヘッダは飛ばしてくれると理解しておけばよい(多分)。

    // 波形一時読み込み用メモリを確保
    char *mFileBuffer = (char *)malloc(dataSize);
    // ファイルから読み込み
    if(AudioFileReadBytes(mAudioFileID, false, 0, &dataSize, mFileBuffer)) {
        NSLog(@"AudioFileReadBytes failed");
        AudioFileClose(mAudioFileID);
        free(mFileBuffer);
        return nil;
    }
    AudioFileClose(mAudioFileID);

mFileBuffer に読み込まれたバイト列は、WAVE ファイルの場合リトルエンディアンになっているものと思われる。

6. バイトオーダー変換

ここまでで終わってしまって後は自力でバイトオーダー変換をしてしまってもよいが、それでは芸がない。中には WAVE なのにビッグエンディアンなデータがあるかもしれないし、このプログラムは何らフォーマットに依存しないから、au とか AIFF とかもちゃんと読めるように作りたい。場合によっては、サンプリングレート変換などをしたくなる時もあるだろう。というわけで、実質的には音ファイルの読み込みとコンバートはいつもセットで使うものとなる。

Core Audio で音データの変換をする場合には、Audio Converter というサービスを利用する。使い方はいたって簡単、入力データの基本情報と出力データの基本情報を AudioConverterNew という関数に食わせると、変換器のインスタンスを作ってくれる。今回は、出力をビッグエンディアンにする以外は入力とまったく同じフォーマットにすればよい。

    // バイトオーダー変換のため、変換先のフォーマットとして BigEndian を指定
    AudioStreamBasicDescription shortSample=mFileDescription;
    shortSample.mFormatFlags =
        mFileDescription.mFormatFlags | kAudioFormatFlagIsBigEndian;

    // バイトオーダー変換用のコンバータを用意
    AudioConverterRef converter;
    if(AudioConverterNew(&mFileDescription, &shortSample, &converter)) {
        NSLog(@"AudioConverterNew failed");
        free(mFileBuffer);
        return nil;
    }

最後に変換を実行する。十分な大きさを持つ short 型の配列 waveform を予め用意しておくこと。

    // バイトオーダー変換
    if(AudioConverterConvertBuffer(converter, dataSize, mFileBuffer,
                                    &dataSize, waveform)) {
        NSLog(@"AudioConverterConvertBuffer failed");
        free(mFileBuffer);
        AudioConverterDispose(converter);
        return nil;
    }

7. 後始末

    // 後始末
    free(mFileBuffer);
    AudioConverterDispose(converter);

8. ビルド

ビルドする時には、Interface Builder の「プロジェクト」→「フレームワークを追加」から AudioToolbox.framework を追加するのを忘れないように。

サンプルプログラム

Project Builder 用のサンプルプロジェクトがダウンロードできます。

ダウンロード (18KB)

音声ファイルを読み込んで先頭の波形を表示するだけ。AIFF でも大丈夫でした。パフォーマンスの最適化、全然やってません。工夫しだいで実用的なプログラムにもなると思います。

FAQ

Q. ネット上の参考文献を教えて。

A. Apple の Audio Developer には役に立つリンクがあります。

Q. 読み込みはわかったけど、書き出しはどうやって?

A. AudioFileCreate を使うという部分以外は似たような手順です。 Writing an AIFF file というページが参考になります。

Q. 読み込んだ音を再生するには? マイクから録音するには?

A. Core Audio のドキュメントを見てください・・・嘘です。多分難しすぎます。しかし、お喜びください。PortAudio という素晴らしいパッケージが存在します! これであなたも、Core Audio に基づいた先進的な Cocoa アプリケーションを簡単に開発できます。なお、おもしろいアプリができたら私にも知らせてください。(^_^)


hiroki@klab.jp
Copyright 2004 Hiroki Mori. All Rights Reserved.

Valid HTML 4.0! Valid CSS!