2021SC@SDUSC
先看一下Audio包。
Audio包下,除开两个第三方的引用,第一个就是具体的Audio源码文件。
The audio service used for music and sound effects playback.
这个类负责音乐和音效的播放。
继承System.Object大类。
重写下列方法:
System.Object.ToString()
System.Object.Equals(System.Object)
System.Object.Equals(System.Object, System.Object)
System.Object.ReferenceEquals(System.Object, System.Object)
System.Object.GetHashCode()
System.Object.GetType()
System.Object.MemberwiseClone()
API_CLASS(Static) class FLAXENGINE_API Audio
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(Audio);
friend class AudioStreamingHandler;
friend class AudioClip;
public:
/// <summary>
/// The audio listeners collection registered by the service.
/// </summary>
static Array<AudioListener*> Listeners;
/// <summary>
/// The audio sources collection registered by the service.
/// </summary>
static Array<AudioSource*> Sources;
/// <summary>
/// The all audio devices.
/// </summary>
API_FIELD(ReadOnly) static Array<AudioDevice> Devices;
/// <summary>
/// Event called when audio devices collection gets changed.
/// </summary>
API_EVENT() static Action DevicesChanged;
/// <summary>
/// Event called when the active audio device gets changed.
/// </summary>
API_EVENT() static Action ActiveDeviceChanged;
public:
/// <summary>
/// Gets the active device.
/// </summary>
/// <returns>The active device.</returns>
API_PROPERTY() static AudioDevice* GetActiveDevice();
/// <summary>
/// Gets the index of the active device.
/// </summary>
/// <returns>The active device index.</returns>
API_PROPERTY() static int32 GetActiveDeviceIndex();
/// <summary>
/// Sets the index of the active device.
/// </summary>
/// <param name="index">The index.</param>
API_PROPERTY() static void SetActiveDeviceIndex(int32 index);
public:
/// <summary>
/// Gets the master volume applied to all the audio sources (normalized to range 0-1).
/// </summary>
/// <returns>The value</returns>
API_PROPERTY() static float GetMasterVolume();
/// <summary>
/// Sets the master volume applied to all the audio sources (normalized to range 0-1).
/// </summary>
/// <param name="value">The value.</param>
API_PROPERTY() static void SetMasterVolume(float value);
/// <summary>
/// Gets the actual master volume (including all side effects and mute effectors).
/// </summary>
/// <returns>The final audio volume applied to all the listeners.</returns>
API_PROPERTY() static float GetVolume();
/// <summary>
/// Sets the doppler effect factor. Scale for source and listener velocities. Default is 1.
/// </summary>
/// <param name="value">The value.</param>
API_PROPERTY() static void SetDopplerFactor(float value);
public:
static void OnAddListener(AudioListener* listener);
static void OnRemoveListener(AudioListener* listener);
static void OnAddSource(AudioSource* source);
static void OnRemoveSource(AudioSource* source);
};
Audio类下面有什么成员呢?
ActiveDevice
表示活动的设备,具体类型是AudioDevice。
ActivedeviceIndex
活动设备的索引,Int32。
Devices
所有音频设备。是一个AudioDevice的数组。
DopplerFactor
设置多普勒效果因子。类型是System.Single
Volume
主音量,包括所有音效。System.Single类型
包括什么触发器呢。
ActiveDeviceChanged
活动音频设备改变时触发
DevicesChanged
音频设备的集合改变时触发
上面这些就是Audio.h中的接口作用。
然后具体地来看Audio.cpp吧。
const Char* ToString(AudioFormat value)
{
switch (value)
{
case AudioFormat::Raw:
return TEXT("Raw");
case AudioFormat::Vorbis:
return TEXT("Vorbis");
default:
return TEXT("");
}
}
直接先来一个重写toString。
判断一下音频的格式,然后分Raw和Vorbis返回对应的格式。那么什么叫Raw和Vorbis呢?
在Types.h里面都定义好了,raw就是直接输出,vorbis就是有损压缩。
Array<AudioListener*> Audio::Listeners;
Array<AudioSource*> Audio::Sources;
Array<AudioDevice> Audio::Devices;
Action Audio::DevicesChanged;
Action Audio::ActiveDeviceChanged;
AudioBackend* AudioBackend::Instance = nullptr;
给一堆成员留好了Array位子,有什么呢,监听器,源,设备,然后是两个事件,上面已经说过了。
然后有个AudioBackend,音频后端,不知道是什么,咱们等一下就知道了。
namespace
{
float MasterVolume = 1.0f;
float Volume = 1.0f;
int32 ActiveDeviceIndex = -1;
bool MuteOnFocusLoss = true;
}
命名空间里面放了这么些参数:
主音量,默认是1
音量,默认1
活动设备索引,默认-1,估计就是不存在
失去焦点时禁音,默认为true
class AudioService : public EngineService
{
public:
AudioService()
: EngineService(TEXT("Audio"), -50)
{
}
bool Init() override;
void Update() override;
void Dispose() override;
};
然后声明了一个AudioService,继承自引擎服务。
AudioService AudioServiceInstance;
紧跟着就把它放进来了,生成一个音频服务实例。
namespace
{
void OnEnginePause()
{
AudioBackend::SetVolume(0.0f);
}
void OnEngineUnpause()
{
AudioBackend::SetVolume(Volume);
}
}
几个方法。干什么的?
引擎暂停时,调用音频后端的方法,把音量置零,很合理。
引擎恢复时,把音量置成volume,但是这个volume没有参数,相当于就是用的上面初始化默认的1.0f。
void AudioSettings::Apply()
{
::MuteOnFocusLoss = MuteOnFocusLoss;
}
把class里面的muteonfocusloss状态应用到AudioSetting里面去。
AudioDevice* Audio::GetActiveDevice()
{
return &Devices[ActiveDeviceIndex];
}
int32 Audio::GetActiveDeviceIndex()
{
return ActiveDeviceIndex;
}
Get音频设备。Get设备索引就返回int32,get设备本身就返回设备集合中的一个设备引用。
void Audio::SetActiveDeviceIndex(int32 index)
{
index = Math::Clamp(index, -1, Devices.Count() - 1);
if (ActiveDeviceIndex == index)
return;
ActiveDeviceIndex = index;
AudioBackend::OnActiveDeviceChanged();
ActiveDeviceChanged();
}
设置活动设备索引。
传入一个int32,然后用了一个clamp方法限定范围,如果index就是当前index,直接返回;
然后设置当前index,调用活动设备改变的事件。
float Audio::GetMasterVolume()
{
return MasterVolume;
}
void Audio::SetMasterVolume(float value)
{
MasterVolume = Math::Saturate(value);
}
float Audio::GetVolume()
{
return Volume;
}
void Audio::SetDopplerFactor(float value)
{
value = Math::Max(0.0f, value);
AudioBackend::SetDopplerFactor(value);
}
一组常规的get、set。
void Audio::OnAddListener(AudioListener* listener)
{
ASSERT(!Listeners.Contains(listener));
if (Listeners.Count() >= AUDIO_MAX_LISTENERS)
{
LOG(Error, "Unsupported amount of the audio listeners!");
return;
}
Listeners.Add(listener);
AudioBackend::Listener::OnAdd(listener);
}
添加监听器的方法。
先用一个断言,保证即将添加的监听器在监听器列表内。
void Audio::OnRemoveListener(AudioListener* listener)
{
if (!Listeners.Remove(listener))
{
AudioBackend::Listener::OnRemove(listener);
}
}
移除监听器。
void Audio::OnAddSource(AudioSource* source)
{
ASSERT(!Sources.Contains(source));
Sources.Add(source);
AudioBackend::Source::OnAdd(source);
}
void Audio::OnRemoveSource(AudioSource* source)
{
if (!Sources.Remove(source))
{
AudioBackend::Source::OnRemove(source);
}
}
一组添加与移除音频源。和监听器的方法类似。
bool AudioService::Init()
{
PROFILE_CPU_NAMED("Audio.Init");
const auto settings = AudioSettings::Get();
const bool mute = CommandLine::Options.Mute.IsTrue() || settings->DisableAudio;
// Pick a backend to use
AudioBackend* backend = nullptr;
#if AUDIO_API_NONE
if (mute)
{
backend = New<AudioBackendNone>();
}
#endif
#if AUDIO_API_PS4
if (!backend)
{
backend = New<AudioBackendPS4>();
}
#endif
#if AUDIO_API_SWITCH
if (!backend)
{
backend = New<AudioBackendSwitch>();
}
#endif
#if AUDIO_API_OPENAL
if (!backend)
{
backend = New<AudioBackendOAL>();
}
#endif
#if AUDIO_API_XAUDIO2
if (!backend)
{
backend = New<AudioBackendXAudio2>();
}
#endif
#if AUDIO_API_NONE
if (!backend)
{
backend = New<AudioBackendNone>();
}
#else
if (mute)
{
LOG(Warning, "Cannot use mute audio. Null Audio Backend not available on this platform.");
}
#endif
if (backend == nullptr)
{
LOG(Error, "Failed to create audio backend.");
return true;
}
AudioBackend::Instance = backend;
LOG(Info, "Audio system initialization... (backend: {0})", AudioBackend::Name());
if (AudioBackend::Init())
{
LOG(Warning, "Failed to initialize audio backend.");
}
Engine::Pause.Bind(&OnEnginePause);
Engine::Unpause.Bind(&OnEngineUnpause);
return false;
}
调用AudioService中的初始化方法。
可以看出,我们的audio后端就是在这里new出来的。
根据不同的音频API,ps4、switch、openAL等等,new不同的音频后端。
然后打印很多对应的log信息。
void AudioService::Update()
{
PROFILE_CPU_NAMED("Audio.Update");
// Update the master volume
float masterVolume = MasterVolume;
if (MuteOnFocusLoss && !Engine::HasFocus)
{
// Mute audio if app has no user focus
masterVolume = 0.0f;
}
if (Math::NotNearEqual(Volume, masterVolume))
{
Volume = masterVolume;
AudioBackend::SetVolume(masterVolume);
}
AudioBackend::Update();
}
刷新音频服务。
如果失焦时禁音,并且失焦,那么禁音。
如果主音量和音量不同,那么将两者同步。
void AudioService::Dispose()
{
ASSERT(Audio::Sources.IsEmpty() && Audio::Listeners.IsEmpty());
// Cleanup
Audio::Devices.Resize(0);
if (AudioBackend::Instance)
{
AudioBackend::Dispose();
Delete(AudioBackend::Instance);
AudioBackend::Instance = nullptr;
}
ActiveDeviceIndex = -1;
}
销毁音频服务。
断言,确保此时没有音频源,也没有音频监听器。
音频设备列表大小改为0.
如果此时有后端,先销毁音频后端。
然后把音频设备索引置-1,和默认值一致。
Audio.cpp就完事了。
下面是AudioBackend.h
// Listener
virtual void Listener_OnAdd(AudioListener* listener) = 0;
virtual void Listener_OnRemove(AudioListener* listener) = 0;
virtual void Listener_VelocityChanged(AudioListener* listener) = 0;
virtual void Listener_TransformChanged(AudioListener* listener) = 0;
截一小段,可以看出来这是一个辅助的handler,有大量的虚函数。
virtual ~AudioBackend()
{
}
析构函数,什么都没做
class Listener
{
public:
FORCE_INLINE static void OnAdd(AudioListener* listener)
{
Instance->Listener_OnAdd(listener);
}
FORCE_INLINE static void OnRemove(AudioListener* listener)
{
Instance->Listener_OnRemove(listener);
}
FORCE_INLINE static void VelocityChanged(AudioListener* listener)
{
Instance->Listener_VelocityChanged(listener);
}
FORCE_INLINE static void TransformChanged(AudioListener* listener)
{
Instance->Listener_TransformChanged(listener);
}
};
具体的Listener类。
规定了添加、移除、修改的方法,全部单例模式强制内联。
然后我们看一下AudioClip.cpp。在看cpp文件之前,发现这个是带API介绍的。那么先跳转到AudioClip.h和对应文档。
Class AudioClip
Audio clip stores audio data in a compressed or uncompressed format using a binary asset. Clips can be provided to audio sources or other audio methods to be played.
音频剪辑使用二进制资源以压缩或未压缩格式存储音频数据。剪辑可以提供给音频源或其他音频方法来播放。
Constructors构造器:
AudioClip()
成员:
Format 获取音频data的格式,类型是AudioFormat
Info 获取音频的元数据,类型AudioDTAINfo
Is3D 判断音频是否是3D的,bool类型。
IsStreamable 判断音频是否使用数据流 bool类型
IsStreamingTaskActive 判断音频流是否异步 bool类型
Length 返回音频片段的长度,单位是秒 类型为system.Single
struct Header
{
AudioFormat Format;
AudioDataInfo Info;
bool Is3D;
bool Streamable;
uint32 OriginalSize;
uint32 ImportedSize;
uint32 SamplesPerChunk[ASSET_FILE_DATA_CHUNKS]; // Amount of audio samples (for all channels) stored per asset chunk (uncompressed data samples count)
};
给音频clip加的一个头部结构。
可以看到包括了音频的信息,格式,3D属性等等。
bool AudioClip::StreamingTask::Run()
{
AssetReference<AudioClip> ref = _asset.Get();
if (ref == nullptr)
{
return true;
}
const auto& queue = ref->StreamingQueue;
auto clip = ref.Get();
// Update the buffers
for (int32 i = 0; i < queue.Count(); i++)
{
const auto idx = queue[i];
uint32& bufferId = clip->Buffers[idx];
if (bufferId == AUDIO_BUFFER_ID_INVALID)
{
AudioBackend::Buffer::Create(bufferId);
}
else
{
// Release unused data
AudioBackend::Buffer::Delete(bufferId);
bufferId = 0;
}
}
// Load missing buffers data
const auto format = clip->Format();
AudioDataInfo info = clip->AudioHeader.Info;
const uint32 bytesPerSample = info.BitDepth / 8;
for (int32 i = 0; i < queue.Count(); i++)
{
const auto idx = queue[i];
const uint32 bufferId = clip->Buffers[idx];
if (bufferId == AUDIO_BUFFER_ID_INVALID)
continue;
byte* data;
uint32 dataSize;
Array<byte> outTmp;
const auto chunk = clip->GetChunk(idx);
if (chunk == nullptr || chunk->IsMissing())
{
LOG(Warning, "Missing audio streaming data chunk.");
return true;
}
// Get raw data or decompress it
switch (format)
{
case AudioFormat::Vorbis:
{
#if COMPILE_WITH_OGG_VORBIS
OggVorbisDecoder decoder;
MemoryReadStream stream(chunk->Get(), chunk->Size());
AudioDataInfo outInfo;
if (decoder.Convert(&stream, outInfo, outTmp))
{
LOG(Warning, "Audio data decode failed (OggVorbisDecoder).");
return true;
}
// TODO: validate decompressed data header info?
data = outTmp.Get();
dataSize = outTmp.Count();
#else
LOG(Warning, "OggVorbisDecoder is disabled.");
return true;
#endif
}
break;
case AudioFormat::Raw:
{
data = chunk->Get();
dataSize = chunk->Size();
}
break;
default:
CRASH;
return true;
}
// Write samples to the audio buffer
info.NumSamples = dataSize / bytesPerSample;
AudioBackend::Buffer::Write(bufferId, data, info);
}
// Update the sources
for (int32 sourceIndex = 0; sourceIndex < Audio::Sources.Count(); sourceIndex++)
{
// TODO: collect refs to audio clip from sources and use faster iteration (but do it thread-safe)
const auto src = Audio::Sources[sourceIndex];
if (src->Clip == clip && src->GetState() == AudioSource::States::Playing)
{
src->RequestStreamingBuffersUpdate();
}
}
return false;
}
实现Run()方法。
如果最后运行成功了,返回false;如果中间哪里失败了,就发一条log,并且返回true。
void AudioClip::StreamingTask::OnEnd()
{
// Unlink
if (_asset)
{
ASSERT(_asset->_streamingTask == this);
_asset->_streamingTask = nullptr;
_asset = nullptr;
}
_dataLock.Release();
// Base
ThreadPoolTask::OnEnd();
}
流任务结束,Onend()时:
首先把asset相关的指针该销毁的销毁,该置为空的置为空;
然后触发线程池的Onend方法。