目次

はじめに
本書の概略
本書の構成
本書の対象読者
免責事項
表記関係について
第1章 AudioObjectの操作
1.1 オーディオオブジェクトからプロパティーのデータを取得する
1.2 プロパティーのデータが必要とするデータサイズを取得する
1.3 オーディオオブジェクトが特定のプロパティーを持っているかどうかを調べる
1.4 オーディオオブジェクトのプロパティーを変更する
1.5 プロパティーが変更された際に通知を受け取る
第2章 オーディオの入出力
2.1 AudioDeviceIOProc
2.2 AudioTimeStamp
2.3 AudioBufferList
2.4 AudioStreamBasicDescription
2.5 IOProcの登録と起動
2.6 IOProcの停止と解除
2.7 サイン波を出力する
第3章 AudioObjectクラス
3.1 BaseClass
3.2 Class
3.3 Owner
3.4 Name
3.5 ModelName
3.6 Manufacturer
3.7 ElementName
3.8 ElementCategoryName
3.9 ElementNumberName
3.10 OwnedObjects
3.11 Identify
3.12 SerialNumber
3.13 FirmwareVersion
第4章 AudioSystemObjectクラス
4.1 Devices
4.2 DefaultInputDevice
4.3 DefaultOutputDevice
4.4 DefaultSystemOutputDevice
4.5 TranslateUIDToDevice
4.6 MixStereoToMono
4.7 PlugInList
4.8 TranslateBundleIDToPlugIn
4.9 TransportManagerList
4.10 TranslateBundleIDToTransportManager
4.11 BoxList
4.12 TranslateUIDToBox
4.13 ClockDeviceList
4.14 TranslateUIDToClockDevice
4.15 ProcessIsMain
4.16 IsInitingOrExiting
4.17 UserIDChanged
4.18 ProcessIsAudible
4.19 SleepingIsAllowed
4.20 UnloadingIsAllowed
4.21 HogModeIsAllowed
4.22 UserSessionIsActiveOrHeadless
4.23 ServiceRestarted
4.24 PowerHint
第5章 AudioDeviceクラス
5.1 ConfigurationApplication
5.2 DeviceUID
5.3 ModelUID
5.4 TransportType
5.5 RelatedDevices
5.6 ClockDomain
5.7 DeviceIsAlive
5.8 DeviceIsRunning
5.9 DeviceCanBeDefaultDevice
5.10 DeviceCanBeDefaultSystemDevice
5.11 Latency
5.12 Streams
5.13 ControlList
5.14 SafetyOffset
5.15 NominalSampleRate
5.16 AvailableNominalSampleRates
5.17 Icon
5.18 IsHidden
5.19 PreferredChannelsForStereo
5.20 PreferredChannelLayout
5.21 PlugIn
5.22 DeviceHasChanged
5.23 DeviceIsRunningSomewhere
5.24 ProcessorOverload
5.25 IOStoppedAbnormally
5.26 HogMode
5.27 BufferFrameSize
5.28 BufferFrameSizeRange
5.29 UsesVariableBufferFrameSizes
5.30 IOCycleUsage
5.31 StreamConfiguration
5.32 IOProcStreamUsage
5.33 ActualSampleRate
5.34 ClockDevice
5.35 IOThreadOSWorkgroup
5.36 ProcessMute
第6章 AudioStreamクラス
6.1 IsActive
6.2 Direction
6.3 TerminalType
6.4 StartingChannel
6.5 Latency
6.6 VirtualFormat
6.7 AvailableVirtualFormats
6.8 PhysicalFormat
6.9 AvailablePhysicalFormats
付録A エラー情報

はじめに

本書の概略

 これは、CoreAudio.frameworkというmacOS向けのオーディオフレームワークについて書かれている本です。

 CoreAudio.frameworkとは、ハードウェア抽象レイヤー(HAL)を介して、オーディオに関するハードウェアとのやりとりに利用するためのインターフェースを提供しているフレームワークです。このフレームワークはサービスとしてのCoreAudioの全ての機能を提供するフレームワークではなく、サービスとしてのCoreAudioが利用する低レイヤーな機能を提供するフレームワークであり、AVAudioToolBox.frameworkやAVFAudio.frameworkにある各機能は、CoreAudio.frameworkの各機能を呼び出す形で実装されています。また、このフレームワークはサービスとしてのCoreAudioに共通するデータ型の定義以外はmacOS向けのものとなっており、iOSやtvOS、watchOSでは利用できません。そして、このフレームワークの詳細をAppleのDeveloper Documentationから探そうとしても、ほとんどの項目に説明がありません。本書は、このフレームワークの中からオーディオ出力に必要となる領域に絞り、そのリファレンスとして機能するようになっています。

 CoreAudio.frameworkは、接続されているデバイスやそのデバイスが持つストリームやコントロール、クロックなどをその概念毎にひとつのクラスとして扱う形を取っており、AudioObjectというクラスを継承関係の親として表現しています。

図1: CoreAudio.frameworkのクラス構造の概念

 これらのクラスのうち、本書で取り扱うオーディオオブジェクトのクラスは以下の4種類です。

 ・AudioObject

 ・AudioSystemObject

 ・AudioDevice

 ・AudioStream

本書の構成

 第1章と第2章で、オーディオオブジェクトの基本的な操作方法やデバイスを通した波形データの出力方法を、サンプルコードを交えながら提示しています。続く第3章、第4章、第5章及び第6章は、先の章で利用した各クラス群それぞれのプロパティーのリファレンスとなっています。これらを活用することにより、CoreAudio.frameworkを利用した、よりローレベルなオーディオプログラミングに取り組むことができるようになるでしょう。なお、本書内にあるサンプルコードはC++言語で記述されています。macOSでの開発はSwiftやObjective-C、Objective-C++を使うことが多いですが、CoreAudio.frameworkはHALを含むローレベルなAPIとなっているため、iOSとの共用部分を除いて、C言語で記述されています。そのため、C言語に近く、メモリー確保や開放などのサンプルには不要なコードを極力なくすため、C++言語を使っています。

 本書を執筆するにあたり利用した環境は、以下のようになっています。

 ・Mac mini (M1, 2020)

 ・macOS Big Sur 11.6.5 (20G527)

 ・Xcode Version 13.2.1 (13C100)

本書の対象読者

 本書は以下のような方々を対象読者として想定しています。

 ・macOSのローレベルオーディオプログラミングに興味がある方

 ・ゲーム開発等、よりハードウェアに近い領域のオーディオプログラミングが必要な方

 ・Appleのオンラインドキュメントに何も書かれていないフレームワークに興味がある方

免責事項

 本書に記載されている内容は、情報提供のみを目的としています。したがって、本書の内容をもとにした実装においては、ご自身の責任と判断によって行ってください。本書の内容をもとにした実装の結果について、著者はいかなる責任も負いません。

表記関係について

 本書に記載されている会社名、製品名などは、一般に各社の登録商標または商標、商品名です。会社名、製品名については、本文中では©、®、™マークなどは表示していません。

第1章 AudioObjectの操作

 オーディオオブジェクト及びそのサブクラスのインスタンスに対して、何かしらの操作をしたい場合、そのインスタンスに紐付けられたAudioObjectID型のハンドルを介して操作します。概念上、全てのクラスはオーディオオブジェクトのサブクラスとなっているため、どのクラスのインスタンスであったとしても、AudioObjectID型の値として扱うことが可能となっています。

1.1 オーディオオブジェクトからプロパティーのデータを取得する

 AudioObjectID型を介して情報を操作するために、AudioObjectGetPropertyData()という関数が、CoreAudio.frameworkには用意されています。

リスト1.1: AudioObjectGetPropertyData()のインターフェース

OSStatus
AudioObjectGetPropertyData(
  AudioObjectID                       inObjectID,
  const AudioObjectPropertyAddress*   inAddress,
  UInt32                              inQualifierDataSize,
  const void*                         inQualifierData,
  UInt32*                             ioDataSize,
  void*                               outData);

 この関数の戻り値の型はOSStatusですが、CoreAudio.frameworkに定義されている関数の戻り値はほとんどOSStatus型となっており、関数の失敗要因のエラーコードを返します。プロダクトコードではこの戻り値を見た上で適切なエラー処理をする必要がありますが、本書でそれを行うと、例示するコードが煩雑になってしまいます。そのため、他書の例に漏れず、必要な場合以外は戻り値の確認は行いません。

 この関数が要求する引数は、それぞれ次のようになっています。

表1.1: AudioObjectGetPropertyData()の引数リファレンス
仮引数名 概要
inObjectID 対象のオーディオオブジェクト
inAddress プロパティーアドレス。対象のプロパティーを指定するための情報
inQualifierDataSize inQualifierDataのバッファサイズ
inQualifierData inAddressだけではプロパティーを特定できない場合の追加情報
ioDataSize 入力時はoutDataのバッファサイズで、関数から戻ってきたときは実際に使用されたバッファのサイズ
outData 要求するプロパティーの実データ

 第二引数のプロパティーアドレスは、指定したオーディオオブジェクトのどのプロパティーに対して操作したいかを表します。

 AudioObjectPropertyAddressは、以下のような要素を持つ構造体として定義されています。

表1.2: AudioObjectPropertyAddressのリファレンス
フィールド名 フィールドの型 概要
mSelector AudioObjectPropertySelector オーディオオブジェクトに関する特定の情報を識別するためのFourCC形式のデータで、プロパティーの一般的な分類を示すセレクター
mScope AudioObjectPropertyScope mSelectorで指定したプロパティーを検索するための範囲となるスコープ
mElement AudioPropertyElement mSelector、mScopeで指定したプロパティーから何番目の要素を取得するかを示すインデックス

 セレクターの型であるAudioObjectPropertySelectorは、その実際の型としてはUInt32で、各クラスが所有しているプロパティーに対応する数値として定義されています。

 たとえば、オーディオオブジェクトクラスのセレクターには、その名前を取得するための kAudioObjectPropertyNameや、オーディオオブジェクトが持っているオーディオオブジェクトの一覧を取得するためのkAudioObjectPropertyOwnedObjectsなどがあります。

 スコープの型であるAudioObjectPropertyScopeもUInt32の別名で、以下の4種類の識別子が定義されています。全てのプロパティーは少なくともGlobalスコープに属しており、その他はプロパティー毎に属し方が異なります。

表1.3: AudioObjectPropertyScopeの種類
Scope名 概要
kAudioObjectPropertyScopeGlobal オブジェクト全体を示すスコープ
kAudioObjectPropertyScopeInput オブジェクトの入力を示すスコープ
kAudioObjectPropertyScopeOutput オブジェクトの出力を示すスコープ
kAudioObjectPropertyScopePlayThrough オブジェクトのプレイスルーを示すスコープ

 最後、エレメントを表すAudioPropertyElement型はUInt32の別名として定義されており、実態としては、配列のインデックスのようなものです。複数のエレメントがある場合にその何番目のエレメントのプロパティーを取得したいのかを指定するためにあります。なお、最初の要素を示す0だけは別名がkAudioObjectPropertyElementMainとして定義されており、全てのプロパティーは少なくともこのメインエレメントを有しています。

 では、実際にプロパティーのデータを取得してみましょう。ここでは例として、デフォルトの出力デバイスを表すAudioObjectIDを取得してみます。

 システムに存在する全てのオーディオオブジェクトのルートとなるオブジェクトは、システムオブジェクトです。このシステムオブジェクトを表すAudioObjectIDとして、kAudioObjectSystemObjectが定義されています。このシステムオブジェクトは、デフォルトの出力デバイスを表すオーディオオブジェクトをプロパティーとして持っているので、システムオブジェクトからデフォルトの出力デバイスのオーディオオブジェクトを取得します。

リスト1.2: システムオブジェクトからデフォルトの出力デバイスを取得する

// (1) ヘッダをインクルード
#include <CoreFoundation/CoreFoundation.h>
#include <CoreAudio/CoreAudio.h>
#include <iostream>

int main()
{
    // (2) 取得したいプロパティーのプロパティーアドレスを設定
    auto address = AudioObjectPropertyAddress
    {
        .mSelector = kAudioHardwarePropertyDefaultOutputDevice,
        .mScope = kAudioObjectPropertyScopeGlobal,
        .mElement = kAudioObjectPropertyElementMain,
    };

    AudioObjectID id = kAudioObjectUnknown; // (3) 取得するAudioObjectIDの受け皿を用意
    UInt32 size = sizeof(id); //(4) 取得するデータサイズを設定

    // (5) AudioObjectIDを取得する
    auto result = AudioObjectGetPropertyData(
                      kAudioObjectSystemObject,
                      &address,
                      0,
                      nullptr,
                      &size,
                      &id);
    if (result == kAudioHardwareNoError)
    {
        std::cout << "device id: " << id << std::endl;
    }
    else
    {
        std::cerr << "error: " << result << std::endl;
    }

    return result;
}

 最初に、(1)で利用するヘッダをインクルードします。CoreAudio.frameworkを利用する場合、インクルードすべきヘッダは以下のふたつです。

 ・CoreFoundation/CoreFoundation.h

 ・CoreAudio/CoreAudio.h

 ヘッダのインクルードをすることで宣言、定義されている型や関数が利用できるようになったところで、(2)のようにデフォルト出力デバイスを取得するためのプロパティーアドレスを作ります。システムオブジェクトのセレクターのスコープは全てグローバルで、エレメントに関してもデフォルト出力デバイスはひとつしかありませんので、メインエレメントを指定します。

 そして、(3)で要求するプロパティーの受け皿としてAudioObjectIDの変数を用意し、(4)でそのサイズもUInt32型の変数として用意します。

 (5)実際にAudioObjectGetPropertyData()を呼び出し、成功すると(3)で用意した変数にデフォルト出力デバイスのAudioObjectIDが入っています。

1.2 プロパティーのデータが必要とするデータサイズを取得する

 取得対象のプロパティーのデータサイズが不変である場合は、先のコード例のように、直接AudioObjectGetPropertyData()関数を呼び出すこともできますが、プロパティーによってはデータサイズが可変の場合もあります。たとえば、システムに接続されているデバイスの一覧を取得したい場合、データサイズはデバイス数に依存するため、一意には定まりません。そういうとき、まずデータサイズだけを取得してからプロパティーのデータを取得する手法を取る必要があります。このデータサイズを取得する専用の関数として、AudioObjectGetPropertyDataSize()が定義されています。

リスト1.3: AudioObjectGetPropertyDataSize()のインターフェース

OSStatus
AudioObjectGetPropertyDataSize(
  AudioObjectID                       inObjectID,
  const AudioObjectPropertyAddress*   inAddress,
  UInt32                              inQualifierDataSize,
  const void*                         inQualifierData,
  UInt32*                             outDataSize);
表1.4: AudioObjectGetPropertyDataSize()の引数リファレンス
仮引数名 概要
inObjectID 対象のオーディオオブジェクト
inAddress プロパティーアドレス
inQualifierDataSize inQualifierDataのバッファサイズ
inQualifierData inAddressだけではプロパティーを特定できない場合の追加情報
outDataSize プロパティーのデータサイズ

 では、実際にシステムに接続されている全てのオーディオデバイスのオーディオオブジェクトを取得してみましょう。

リスト1.4: システムに接続されている全てのオーディオデバイスの取得

// (1) プロパティーアドレスを設定
auto address = AudioObjectPropertyAddress
{
    .mSelector = kAudioHardwarePropertyDevices,
    .mScope = kAudioObjectPropertyScopeGlobal,
    .mElement = kAudioObjectPropertyElementMain,
};

// (2) データサイズを取得
UInt32 size = 0;
AudioObjectGetPropertyDataSize(
    kAudioObjectSystemObject,
    &address,
    0,
    nullptr,
    &size);

// (3) オーディオデバイスのリストを格納するメモリーを確保して
auto device_list = std::vector<AudioObjectID>(size / sizeof(AudioObjectID));

// (4) オーディオデバイスのリストを取得する
AudioObjectGetPropertyData(
    kAudioObjectSystemObject,
    &address,
    0,
    nullptr,
    &size,
    &device_list[0]);

for (auto device_id : device_list)
{
    std::cout << "device id: " << device_id << std::endl;
}

 基本的な流れは先程と同じですが、このプロパティーはデータサイズがシステムの状態によって異なりますので、プロパティーのデータを取得する前に、(2)でデータサイズを確定させています。

試し読みはここまでです。
この続きは、製品版でお楽しみください。