LoginSignup
11
10

More than 5 years have passed since last update.

AudioQueueServicesを使用した音源の再生

Last updated at Posted at 2014-09-07

解説されているサイトを見ても分かりにくかったので自分で実装してみました。

詳細は勉強中ですので、随時更新していきます!

概要

音源を再生するにはデータファイルをディスクから取得し、それをオーディを出力デバイスに送る訳ですが、例えば1GBのファイルを全て読み込んでから再生するのでは少し時間がかかり、その分再生までに遅延が発生してしまいます。その遅延を最小限にする為に、ファイルを少しずつ読み込み再生するという手法を使っています。

詳しくは、Audio Queue Architectureの図を見てもらえればわかると思います。

再生する音源に関して

  • CDクオリティー(16bitで44100Hz)のステレオ録音

サンプリング周波数に関して

再生手順

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
11
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
10