解説されているサイトを見ても分かりにくかったので自分で実装してみました。
詳細は勉強中ですので、随時更新していきます!
概要
音源を再生するにはデータファイルをディスクから取得し、それをオーディを出力デバイスに送る訳ですが、例えば1GBのファイルを全て読み込んでから再生するのでは少し時間がかかり、その分再生までに遅延が発生してしまいます。その遅延を最小限にする為に、ファイルを少しずつ読み込み再生するという手法を使っています。
詳しくは、Audio Queue Architectureの図を見てもらえればわかると思います。
再生する音源に関して
- CDクオリティー(16bitで44100Hz)のステレオ録音
サンプリング周波数に関して
- アナログの波形をデジタルで再現する為に、1秒間に何個サンプリングするか
- 44100Hz
- 44100個波形をサンプリングする
- 参考:サンプリング定理/エイリアシング - DSP の基礎・トレーニング - TI
再生手順
Audio Queueサービスを使用して音源を再生する場合下記のような手順になります。
- オーディオファイルを読み込む // 1
- オーディオキューを作成する
- オーディオデータのフォーマット情報を取得する // 2
- 再生用のオーディオキューオブジェクトを作成する // 3
- パケットの最大バイト数(ファイル容量)を取得 // 4
- 何パケットずつ読み込むか計算する // 5
- バケット毎の詳細情報をセットする // 6
- 再生 // 7
ソース
非圧縮の音源を単純に再生するだけのサンプルです
ヘッダファイル
# import <AudioToolbox/AudioToolbox.h>
# define kNumberBuffers 3
# define kBufferSeconds 0.5 // 0.5秒ずつバッファに入れる
@interface AudioQueuePlayer : NSObject
{
NSURL *filepath;
AudioStreamBasicDescription audioDataFormat;
AudioQueueRef audioQueue;
AudioQueueBufferRef audioBuffers[kNumberBuffers];
AudioFileID inAudioFile;
AudioStreamPacketDescription *audioPacketDesc;
UInt32 indexPacket;
UInt32 numPacketsToRead;
UInt32 playStatus;
}
- (id)initWithFilepath:(NSURL *)path;
- (void)play;
@end
実装ファイル
# import "AudioQueuePlayer.h"
@implementation AudioQueuePlayer
// コールバック関数
static void AQOutputCallback(void *userData,
AudioQueueRef audioQueueRef,
AudioQueueBufferRef audioQueueBufferRef) {
AudioQueuePlayer *player = (__bridge AudioQueuePlayer*)userData;
[player audioQueueOutputWithQueue:audioQueueRef queueBuffer:audioQueueBufferRef];
}
- (id)initWithFilepath:(NSURL *)path {
indexPacket = 0;
filepath = path;
// オーディオキューを作成する
[self createAudioQueue];
// 再生の事前準備をする
[self prepareToPlay];
return self;
}
- (void)createAudioQueue {
UInt32 propertySize;
// 再生するオーディオファイルを読み込み権限で開く
AudioFileOpenURL((CFURLRef)CFBridgingRetain(filepath),
kAudioFileReadPermission,
0,
&inAudioFile);
// オーディオデータフォーマットの情報をaudioDataFormatへセット
propertySize = sizeof(audioDataFormat);
AudioFileGetProperty(inAudioFile,
kAudioFilePropertyDataFormat,
&propertySize,
&audioDataFormat);
// 再生用のオーディオキューオブジェクトを作成する
AudioQueueNewOutput(
&audioDataFormat, // AudioStreamBasicDescription
AQOutputCallback, // AudioQueueOutputCallback
(void *)CFBridgingRetain(self), // コールバックの第一引数に渡される
nil,
nil,
0,
&audioQueue);
}
- (void)prepareToPlay {
UInt32 propertySize;
// パケットの最大バイト数を取得
UInt32 maxPacketSize;
propertySize = sizeof(maxPacketSize);
AudioFileGetProperty(
inAudioFile,
kAudioFilePropertyPacketSizeUpperBound,
&propertySize,
&maxPacketSize);
// 毎秒のパケット数
Float64 numPacketsPerSecond;
numPacketsPerSecond = audioDataFormat.mSampleRate / audioDataFormat.mFramesPerPacket;
UInt32 bufferSize;
bufferSize = numPacketsPerSecond * maxPacketSize * kNumberBuffers;
numPacketsToRead = numPacketsPerSecond * kBufferSeconds;
audioPacketDesc = malloc(numPacketsToRead * sizeof(AudioStreamPacketDescription));
// バッファを作成
for (int i = 0; i < kNumberBuffers; i++) {
AudioQueueAllocateBuffer(audioQueue, bufferSize, &audioBuffers[i]);
}
}
- (void) audioQueueOutputWithQueue:(AudioQueueRef)audioQueueRef
queueBuffer:(AudioQueueBufferRef)audioQueueBufferRef {
UInt32 numBytes;
UInt32 numPackets = numPacketsToRead;
// パケットを読み込む
AudioFileReadPackets(inAudioFile,
NO,
&numBytes,
audioPacketDesc,
indexPacket,
&numPackets,
audioQueueBufferRef->mAudioData);
if (numPackets > 0) {
audioQueueBufferRef->mAudioDataByteSize = numBytes;
AudioQueueEnqueueBuffer(audioQueueRef,
audioQueueBufferRef,
numPackets,
audioPacketDesc);
// 次のパケットを読み込むようにする
indexPacket += numPackets;
}
}
-(void)play {
for(int i=0; i<kNumberBuffers; i++){
[self audioQueueOutputWithQueue:audioQueue queueBuffer:audioBuffers[i]];
}
AudioQueueStart(audioQueue, nil);
}
@end