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 自体も機能が盛りだくさんで、必要十分な情報にアクセスするのはなかなか難しい。目的をしぼったレシピは、きっと後で何かの役に立つだろう。
#import <AudioToolbox/AudioFile.h>
#import <AudioToolbox/AudioConverter.h>
Audio Toolbox は Core Audio を構成するフレームワークのひとつ。
当然ながら、まず 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 の関数だが、別に気にしなくてよい。
AudioFileID mAudioFileID;
// ファイルを開く
if(AudioFileOpen(&ref, fsRdPerm, 0, &mAudioFileID)) {
NSLog(@"AudioFileOpen failed");
return nil;
}
ヘッダ情報のようなもの。まず、基本情報全体を 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;
いよいよ読み込み。感覚的には、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 ファイルの場合リトルエンディアンになっているものと思われる。
ここまでで終わってしまって後は自力でバイトオーダー変換をしてしまってもよいが、それでは芸がない。中には 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;
}
// 後始末
free(mFileBuffer);
AudioConverterDispose(converter);
ビルドする時には、Interface Builder の「プロジェクト」→「フレームワークを追加」から AudioToolbox.framework を追加するのを忘れないように。
Project Builder 用のサンプルプロジェクトがダウンロードできます。
ダウンロード (18KB)音声ファイルを読み込んで先頭の波形を表示するだけ。AIFF でも大丈夫でした。パフォーマンスの最適化、全然やってません。工夫しだいで実用的なプログラムにもなると思います。
Q. ネット上の参考文献を教えて。
A. Apple の Audio Developer には役に立つリンクがあります。
Q. 読み込みはわかったけど、書き出しはどうやって?
A. AudioFileCreate を使うという部分以外は似たような手順です。 Writing an AIFF file というページが参考になります。
Q. 読み込んだ音を再生するには? マイクから録音するには?
A. Core Audio のドキュメントを見てください・・・嘘です。多分難しすぎます。しかし、お喜びください。PortAudio という素晴らしいパッケージが存在します! これであなたも、Core Audio に基づいた先進的な Cocoa アプリケーションを簡単に開発できます。なお、おもしろいアプリができたら私にも知らせてください。(^_^)