JUCEのAudioIODeviceについての覚え書き
オーディオアプリケーションフレームワークであるJUCEでは、様々なプラットフォームネイティブなオーディオAPI(オーディオドライバ)をサポートしています。これらの実装はjuce_audio_devices
モジュールに置かれています。
公式リファレンス: juce_audio_devices
サポートするオーディオAPI(一例)
- Windows: DirectSound, WASAPI, ASIO
- macOS: CoreAudio
- Linux: ALSA, JACK
さて、現状でも十分なくらいのオーディオAPIを公式にサポートしているJUCEですが、何らかの事情で公式にサポートされていないオーディオAPIで動作するアプリケーションを実装したいとなった場合はどうしましょう?そのような状況にも対応できるように、JUCEでは開発者が独自にオーディオデバイスを追加できる仕組みが用意されています。
JUCEにおけるオーディオ処理の仕組み
まずは前提の知識として、JUCEのオーディオ処理の仕組みについて確認しておきましょう。
JUCEのオーディオに関する機能は複数のクラスに分散されており、それらが協調して動作することで、オーディオAPIとのコミュニケーションから信号処理までを分散して各クラスに分割して実装することができます。
JUCEにおけるオーディオデバイスに関連するクラス群の一覧についてはらぴすさんの記事で詳細が書かれているので、そちらを参照するとよいでしょう。JUCEでのオーディオデバイスの扱い方
JUCEでは、オーディオ処理を管理するAudioDeviceManager
というクラスが提供されます。このクラスはオーディオデバイスを抽象化したクラスAudioIODevice
とオーディオ処理(信号処理・オーディオバッファ操作)を抽象化したクラスAudioIODeviceCallback
の橋渡し役も担っています。
次の図は、AudioDeviceManager
クラス、AudioIODevice
クラス、AudioIODeviceCallback
クラスがどのようなやり取りを行っているかを図にしたものです。AudioDeviceManager
がAudioIODevice
のデバイス作成やオーディオスレッドの開始を実行し、AudioIODevice
内で走っているオーディオスレッドからのコールバックに基づいてAudioDeviceManager
登録されたAudioIODeviceCallback
を継承したオブジェクトに対してコールバック関数が実行されるという仕組みになっています。
オーディオデバイスを定義するクラス
オーディオデバイスに関連するクラス群のうち、オーディオAPIの抽象化に関係するクラスは主に次のクラスになります。
-
AudioIODevice
: プラットフォームネイティブなオーディオAPIの実装を定義する。公式リファレンス -
AudioIODeviceType
:AudioIODevice
の”型情報”みたいなもの。インスタンス生成やメタ情報を定義する。公式リファレンス
独自のオーディオデバイスをアプリケーションに追加したい場合、以下の作業を行うことで、JUCEのフレームワークに独自のオーディオデバイスを組み込んで使用することができます。
-
AudioIODevice
とAudioIODeviceType
をそれぞれ継承したクラスを実装する。 - プログラム実行時、独自に追加した
AudioIODeviceType
の子クラスをAudioDeviceManager
に登録する。 -
AudioDeviceManager
を介して独自に追加したオーディオデバイスを生成する
次の図は、AudioIODevice
クラス、AudioIODeviceType
クラス、AudioDeviceManager
クラスがどのようなやり取りを行っているかを図にしたものです。AudioIODeviceType
を継承したMyAudioIODeviceType
クラスをAudioDeviceManager
に登録することで、AudioDeviceManager
からMyAudioIODeviceType
に対してMyAudioIODevice
のインスタンス生成とオーディオスレッドの開始を実行することができます。MyAudioIODevice
が生成されたら引き続きオーディオスレッドの開始を行うことで、MyAudioIODevice
内のオーディオスレッドからのコールバックに基づいてAudioDeviceManager
に対してコールバック関数が実行されるという仕組みになっています。
Sine波を入力するSineWave Audio Device
の実装例
ここでは実例として、SineWave Audio Device
を例に、JUCEに独自のオーディオデバイスを追加するまでのソースコードを載せておきます。
このSineWave Audio Device
は、毎フレーム常に入力バッファへSine波を入力するといった処理を行いますが、AudioSourcePlayer
クラスやAudioProcessorPlayer
クラスのaudioDeviceIOCallback関数
から戻される出力バッファの中身を受け取った後はプラットフォームネイティブなオーディオAPIに渡すことはしていません。そのため、このオーディオデバイスを選択した場合、スピーカーに音声は流れません。
■ SineWaveAudioIODevice.h
#pragma once
#include "../JuceLibraryCode/JuceHeader.h"
// AudioIODeviceクラスを継承する
// オーディオスレッドとしてTimerクラスを継承する
class SineWaveAudioIODevice : public AudioIODevice, private Timer
{
public:
SineWaveAudioIODevice(const String& deviceName, const String& typeName, const String& outputDeviceID, const String& inputDeviceID);
~SineWaveAudioIODevice() override;
// AudioIODeviceクラスで宣言された関数(インターフェース)をオーバーライドする
virtual StringArray getOutputChannelNames() override { return { "Out1", "Out2", "Out3", "Out4" }; };
virtual StringArray getInputChannelNames() override { return{ "In1", "In2", "In3", "In4" }; };
virtual Array<double> getAvailableSampleRates() override;
virtual Array<int> getAvailableBufferSizes() override;
virtual int getDefaultBufferSize() override;
virtual String open(const BigInteger& inputChannels, const BigInteger& outputChannels, double sampleRate, int bufferSizeSamples) override;
virtual void close() override;
virtual bool isOpen() override;
virtual void start(AudioIODeviceCallback* callback) override;
virtual void stop() override;
virtual bool isPlaying() override;
virtual String getLastError() override;
virtual int getCurrentBufferSizeSamples() override { return bufferSize; };
virtual double getCurrentSampleRate() override { return sampleRate; };
virtual int getCurrentBitDepth() override { return bitDepth; };
virtual BigInteger getActiveOutputChannels() const override { return channelsOut; };
virtual BigInteger getActiveInputChannels() const override { return channelsIn; };
virtual int getOutputLatencyInSamples() override { return 0; };
virtual int getInputLatencyInSamples() override { return 0; };
private:
// Timerクラスのメンバ関数(インターフェース)をオーバーライドする
virtual void timerCallback() override;
int bufferSize{ 512 };
double sampleRate{ 44100 };
int bitDepth{ 32 };
BigInteger channelsIn, channelsOut;
int numChannelsIn, numChannelsOut;
CriticalSection audioDeviceLock;
bool closed{ false };
// AudioDeviceMangerで保持しているコールバック先のオブジェクト
AudioIODeviceCallback* deviceManagerCallbackHandler{ nullptr };
};
// AudioIODeviceTypeクラスを継承する
class SineWaveAudioIODeviceType : public AudioIODeviceType
{
public:
SineWaveAudioIODeviceType()
: AudioIODeviceType("SineWave Audio Device")
{};
~SineWaveAudioIODeviceType() override
{};
// AudioIODeviceTypeクラスのメンバ関数(インターフェース)をオーバーライドする
virtual void scanForDevices() override {}
virtual StringArray getDeviceNames(bool wantInputNames = false) const override;
virtual int getDefaultDeviceIndex(bool forInput) const override;
virtual int getIndexOfDevice(AudioIODevice* device, bool asInput) const override;
virtual bool hasSeparateInputsAndOutputs() const override { return true; };
virtual AudioIODevice* createDevice(const String& outputDeviceName, const String& inputDeviceName) override;
private:
StringArray inputDeviceNames { "SineWave Input1", "SineWave Input2" };
StringArray outputDeviceNames{ "SineWave Output1", "SineWave Output2" };
};
■ SineWaveAudioIODevice.cpp
#include "SineWaveAudioIODevice.h"
SineWaveAudioIODevice::SineWaveAudioIODevice(const String & deviceName, const String & typeName, const String & outputDeviceID, const String & inputDeviceID)
: AudioIODevice(deviceName, typeName)
{
}
SineWaveAudioIODevice::~SineWaveAudioIODevice()
{
}
Array<double> SineWaveAudioIODevice::getAvailableSampleRates()
{
Array<double> ret;
ret.add(44100);
ret.add(48000);
ret.add(88200);
ret.add(96000);
return ret;
}
Array<int> SineWaveAudioIODevice::getAvailableBufferSizes()
{
Array<int> ret;
ret.add(256);
ret.add(512);
ret.add(1024);
return ret;
}
int SineWaveAudioIODevice::getDefaultBufferSize()
{
return bufferSize;
}
String SineWaveAudioIODevice::open(const BigInteger & inputChannels, const BigInteger & outputChannels, double sampleRate_, int bufferSizeSamples)
{
ScopedLock lock(audioDeviceLock);
sampleRate = sampleRate_;
channelsIn = inputChannels;
channelsOut = outputChannels;
bufferSize = bufferSizeSamples;
numChannelsIn = channelsIn.countNumberOfSetBits();
numChannelsOut = channelsOut.countNumberOfSetBits();
closed = false;
return String();
}
void SineWaveAudioIODevice::close()
{
stop();
closed = true;
}
bool SineWaveAudioIODevice::isOpen()
{
return ! closed;
}
void SineWaveAudioIODevice::start(AudioIODeviceCallback * callback)
{
// AudioDeviceManagerのインスタンスに自身のオーディオデバイスを渡すとともに、オーディオスレッドが開始したことを通知する
deviceManagerCallbackHandler = callback;
deviceManagerCallbackHandler->audioDeviceAboutToStart(this);
startTimerHz(30);
}
void SineWaveAudioIODevice::stop()
{
// AudioDeviceManagerのインスタンスにオーディオスレッドが停止したことを通知する
if (deviceManagerCallbackHandler != nullptr)
{
ScopedLock lock(audioDeviceLock);
stopTimer();
deviceManagerCallbackHandler->audioDeviceStopped();
deviceManagerCallbackHandler = nullptr;
}
}
bool SineWaveAudioIODevice::isPlaying()
{
return deviceManagerCallbackHandler != nullptr;
}
String SineWaveAudioIODevice::getLastError()
{
return String();
}
// このオーディオデバイスのオーディオスレッドループ
void SineWaveAudioIODevice::timerCallback()
{
AudioBuffer<float> inputBuffer(numChannelsIn, bufferSize);
AudioBuffer<float> outputBuffer(numChannelsOut, bufferSize);
// audioDeviceIOCallback()に渡すSine波のサンプルデータを生成する
for (int ch_index = 0; ch_index < inputBuffer.getNumChannels(); ++ch_index)
{
auto ch_data = inputBuffer.getWritePointer(ch_index);
for (int sample = 0; sample < inputBuffer.getNumSamples(); ++sample)
{
ch_data[sample] = sinf(juce::MathConstants<float>::twoPi * sample / inputBuffer.getNumSamples()) * 0.5f;
}
}
// AudioDeviceManagerを介してオーディオバッファの更新を実行する
deviceManagerCallbackHandler->audioDeviceIOCallback(
inputBuffer.getArrayOfReadPointers(), inputBuffer.getNumChannels(),
outputBuffer.getArrayOfWritePointers(), outputBuffer.getNumChannels(),
bufferSize
);
}
//==============================================================================
StringArray SineWaveAudioIODeviceType::getDeviceNames(bool wantInputNames) const
{
if(wantInputNames)
return inputDeviceNames;
else
return outputDeviceNames;
}
int SineWaveAudioIODeviceType::getDefaultDeviceIndex(bool forInput) const
{
return 0;
}
int SineWaveAudioIODeviceType::getIndexOfDevice(AudioIODevice * device, bool asInput) const
{
if (asInput) {
switch (device->getActiveInputChannels().getHighestBit())
{
case 0:
case 1:
return 0;
break;
case 2:
case 3:
return 1;
break;
default:
return -1;
break;
}
} else {
switch (device->getActiveOutputChannels().getHighestBit())
{
case 0:
case 1:
return 0;
break;
case 2:
case 3:
return 1;
break;
default:
return -1;
break;
}
}
}
// SineWaveAudioIODeviceのインスタンスを生成するメンバ関数
AudioIODevice * SineWaveAudioIODeviceType::createDevice(const String & outputDeviceName, const String & inputDeviceName)
{
std::unique_ptr<SineWaveAudioIODevice> device;
device.reset(new SineWaveAudioIODevice(outputDeviceName, getTypeName(), outputDeviceName, inputDeviceName));
return device.release();
}
■ MainComponent.cpp
#include "MainComponent.h"
#include "SineWaveAudioIODevice.h"
MainComponent::MainComponent()
{
// AudioDeviceManagerに独自のオーディオデバイスを追加登録する
deviceManager.addAudioDeviceType(new SineWaveAudioIODeviceType());
setSize(800, 600);
}
サンプル実装例を組み込んだアプリケーションの動作例
前述の通りSineWave Audio Device
の出力バッファに値を渡してもスピーカーから音が聞こえてくることはないため、実際の信号を耳で確認することはできません。ここでは、オーディオビジュアライザを利用して視覚的に信号を確認することにしました。
Author And Source
この問題について(JUCEのAudioIODeviceについての覚え書き), 我々は、より多くの情報をここで見つけました https://qiita.com/COx2/items/72e411478156f6d869bc著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .