Core Audioにおいて、オーディオデータの状態(リニアPCM等のフォーマットとか、ビットやサンプリングレートとか、チャンネル数とか)を表す構造体がAudioStreamBasicDescriptionです。
struct AudioStreamBasicDescription
{
Float64 mSampleRate; //サンプリング周波数(1秒間のフレーム数)
UInt32 mFormatID; //フォーマットID(リニアPCM、MP3、AACなど)
UInt32 mFormatFlags; //フォーマットフラグ(エンディアン、整数or浮動小数点数)
UInt32 mBytesPerPacket; //1パケット(データを読み書きする単位)のバイト数
UInt32 mFramesPerPacket; //1パケットのフレーム数
UInt32 mBytesPerFrame; //1フレームのバイト数
UInt32 mChannelsPerFrame; //1フレームのチャンネル数
UInt32 mBitsPerChannel; //1チャンネルのビット数
UInt32 mReserved; //意味なし。アラインメントを揃えるためのもの?
};
typedef struct AudioStreamBasicDescription AudioStreamBasicDescription;
フォーマットIDは以下のような定数が定義されています。
enum
{
kAudioFormatLinearPCM = 'lpcm',
kAudioFormatAC3 = 'ac-3',
kAudioFormat60958AC3 = 'cac3',
kAudioFormatAppleIMA4 = 'ima4',
kAudioFormatMPEG4AAC = 'aac ',
kAudioFormatMPEG4CELP = 'celp',
kAudioFormatMPEG4HVXC = 'hvxc',
kAudioFormatMPEG4TwinVQ = 'twvq',
kAudioFormatMACE3 = 'MAC3',
kAudioFormatMACE6 = 'MAC6',
kAudioFormatULaw = 'ulaw',
kAudioFormatALaw = 'alaw',
kAudioFormatQDesign = 'QDMC',
kAudioFormatQDesign2 = 'QDM2',
kAudioFormatQUALCOMM = 'Qclp',
kAudioFormatMPEGLayer1 = '.mp1',
kAudioFormatMPEGLayer2 = '.mp2',
kAudioFormatMPEGLayer3 = '.mp3',
kAudioFormatTimeCode = 'time',
kAudioFormatMIDIStream = 'midi',
kAudioFormatParameterValueStream = 'apvs',
kAudioFormatAppleLossless = 'alac',
kAudioFormatMPEG4AAC_HE = 'aach',
kAudioFormatMPEG4AAC_LD = 'aacl',
kAudioFormatMPEG4AAC_HE_V2 = 'aacp',
kAudioFormatMPEG4AAC_Spatial = 'aacs',
kAudioFormatAMR = 'samr'
};
普通にオーディオデータを扱うときは、WAVやAIFFでおなじみの非圧縮フォーマットであるリニアPCM(kAudioFormatLinearPCM)になります。その他は、それぞれの圧縮フォーマットのファイルの読み書きをするときに使用します。オーディオファイルのフォーマット以外にもタイムコードやMIDIやParameterValueStreamなんてのがあるのがちょっと面白そうなところです。
フォーマットフラグは以下の定数が定義されています。
enum
{
kAudioFormatFlagIsFloat = (1L << 0),
kAudioFormatFlagIsBigEndian = (1L << 1),
kAudioFormatFlagIsSignedInteger = (1L << 2),
kAudioFormatFlagIsPacked = (1L << 3),
kAudioFormatFlagIsAlignedHigh = (1L << 4),
kAudioFormatFlagIsNonInterleaved = (1L << 5),
kAudioFormatFlagIsNonMixable = (1L << 6),
kAudioFormatFlagsAreAllClear = (1L << 31),
kLinearPCMFormatFlagIsFloat = kAudioFormatFlagIsFloat,
kLinearPCMFormatFlagIsBigEndian = kAudioFormatFlagIsBigEndian,
kLinearPCMFormatFlagIsSignedInteger = kAudioFormatFlagIsSignedInteger,
kLinearPCMFormatFlagIsPacked = kAudioFormatFlagIsPacked,
kLinearPCMFormatFlagIsAlignedHigh = kAudioFormatFlagIsAlignedHigh,
kLinearPCMFormatFlagIsNonInterleaved = kAudioFormatFlagIsNonInterleaved,
kLinearPCMFormatFlagIsNonMixable = kAudioFormatFlagIsNonMixable,
kLinearPCMFormatFlagsAreAllClear = kAudioFormatFlagsAreAllClear,
kAppleLosslessFormatFlag_16BitSourceData = 1,
kAppleLosslessFormatFlag_20BitSourceData = 2,
kAppleLosslessFormatFlag_24BitSourceData = 3,
kAppleLosslessFormatFlag_32BitSourceData = 4
};
enum
{
#if TARGET_RT_BIG_ENDIAN
kAudioFormatFlagsNativeEndian = kAudioFormatFlagIsBigEndian,
#else
kAudioFormatFlagsNativeEndian = 0,
#endif
kAudioFormatFlagsCanonical =
kAudioFormatFlagIsFloat |
kAudioFormatFlagsNativeEndian |
kAudioFormatFlagIsPacked,
kAudioFormatFlagsNativeFloatPacked =
kAudioFormatFlagIsFloat |
kAudioFormatFlagsNativeEndian |
kAudioFormatFlagIsPacked
};
フラグですから、設定したい定数をビット演算で組み合わせて指定します。例としてAIFFの16bit整数のオーディオファイルを作成する場合には、ビッグエンディアンで符号付き整数でPackedになりますから、
AudioStreamBasicDescription desc;
desc.mFormatFlags =
kAudioFormatFlagIsBigEndian |
kLinearPCMFormatFlagIsSignedInteger |
kAudioFormatFlagIsPacked;
といった感じになります。
Core Audioではデフォルトだとオーディオデータは32bitのFloatのPackedのネイティブなエンディアンで扱われますから、それらが既に組み合わせられた、kAudioFormatFlagsNativeFloatPackedなんていう便利な定数も用意されています。
ちなみにPackedとは何かというと、オーディオデータの1サンプルに割り当てられたデータ領域の全てのビットを使った状態です。例えば、オーディオデータの1サンプルに32bitのメモリ領域が割り当てられているときに、ぴったり32bitのデータが入っている状態がPackedという事になります。32bitのメモリ領域に20bitのデータが入っているような場合はPackedはセットせず、その20bitのデータが上位ビットに寄せられていればAlignedHigh、下位ビットに寄せられていればAlignedLow(AlignedHighをセットしない)になります。
Core Audioでオーディオデータを扱うときには基本的にPackedなので、Packedでない状態が実際に使われているところがないかと探したら、自分の使っているMacBook Proのオーディオデバイスがそうでした。デバイスのビットを20bitや24bitに設定したときにはpackedではなく、32bitの領域が割り当てられていてAlignedLowになっています。とはいってもデバイス側のフォーマットなので、CoreAudio経由で使うときは基本的に32bitFloatに変換された状態で渡ってきますから、実際に意識する事はないと思います。
mBytesPerPacketからmBitsPerChannelまでの5つのメンバは、オーディオのデータがどんな状態で並んでいるかが表されます。
リニアPCMでのそれぞれの関係性を見ていくと、mBitsPerChannelで指定されたビット数の1サンプルをチャンネル数分まとめたものがフレームで、そのフレームをまとめて一回分の読み書きの単位としているのがパケットです。
mChannelsPerFrameで1フレーム内のチャンネル数、mBytesPerFrameで1フレームの容量、mFramesPerPacketで1パケット内のフレーム数、mBytesPerPacketで1パケットの容量が表される事になります。
リニアPCMならフレーム単位で一つのデータが成立するのでmFramesPerPacketが1となり、他の全てのメンバにもフォーマットに応じた値が設定されますが、圧縮フォーマットであれば、いくらかのフレームがまとめられて1パケットに圧縮されているので、mFramesPerPacketとmChannelsPerFrameのみが設定され、他の値は0という場合もあります。
リニアPCMでPackedの場合、5つのうち3つ決まれば残り2つは自然と値が計算で求められます。たとえば32bitのステレオのInterleavedだと、
desc.mBitsPerChannel = 32; desc.mFramesPerPacket = 1; desc.mChannelsPerFrame = 2; desc.mBytesPerFrame = desc.mBitsPerChannel / 8 * desc.mChannelsPerFrame; desc.mBytesPerPacket = desc.mBytesPerFrame * desc.mFramesPerPacket;
という感じです。
例として、オーディオファイルでよく使われそうなフォーマットの設定値を載せておきます。
// AIFF 16bit 44.1kHz STEREOの場合
AudioStreamBasicDescription aiffFormat;
aiffFormat.mSampleRate = 44100.0;
aiffFormat.mFormatID = kAudioFormatLinearPCM;
aiffFormat.mFormatFlags =
kAudioFormatFlagIsBigEndian |
kAudioFormatFlagIsSignedInteger |
kAudioFormatFlagIsPacked;
aiffFormat.mBitsPerChannel = 16;
aiffFormat.mChannelsPerFrame = 2;
aiffFormat.mFramesPerPacket = 1;
aiffFormat.mBytesPerFrame = 4;
aiffFormat.mBytesPerPacket = 4;
aiffFormat.mReserved = 0;
// WAVE 8bit 48kHz MONOの場合
AudioStreamBasicDescription wavFormat;
wavFormat.mSampleRate = 48000.0;
wavFormat.mFormatID = kAudioFormatLinearPCM;
wavFormat.mFormatFlags = kAudioFormatFlagIsPacked; //WAVの8bitはunsigned
wavFormat.mBitsPerChannel = 8;
wavFormat.mChannelsPerFrame = 1;
wavFormat.mFramesPerPacket = 1;
wavFormat.mBytesPerFrame = 1;
wavFormat.mBytesPerPacket = 1;
wavFormat.mReserved = 0;
// AAC 44.1kHz STEREOの場合
AudioStreamBasicDescription m4aFormat;
m4aFormat.mSampleRate = 44100.0;
m4aFormat.mFormatID = kAudioFormatMPEG4AAC;
m4aFormat.mFormatFlags = kAudioFormatFlagIsBigEndian;
m4aFormat.mBytesPerPacket = 0;
m4aFormat.mFramesPerPacket = 1024;
m4aFormat.mBytesPerFrame = 0;
m4aFormat.mChannelsPerFrame = 2;
m4aFormat.mBitsPerChannel = 0;
m4aFormat.mReserved = 0;
前回から見てきたAudioBufferListやAudioStreamBasicDescriptionが扱えれば、オーディオデバイスやオーディオファイル等と、オーディオデータやフォーマット情報のやり取りが出来るようになります。その方法については次回やってみたいと思います。