载入中...
搜索中...
未找到
Audio.cpp
浏览该文件的文档.
1#include "Audio.h"
2#include "Source.h"
3
4#include "AudioCapabilities.h"
5#include "AudioZone.h"
6#include "EventSinkOwner.h"
7#include "UnderwaterAudio.h"
8#include "common/Exception.h"
9#include "common/Profile.h"
12#include "sound/Decoder.h"
13#include "sound/Sound.h"
14#include "sound/SoundData.h"
15
16#include <AL/al.h>
17#include <simplesquirrel/simplesquirrel.hpp>
18
19#include <algorithm>
20#include <chrono>
21
22namespace eve {
23namespace audio {
24
26
29 StartupStage stage("audio: OpenAL device/context + worker thread");
30 device = alcOpenDevice(nullptr);
31 if (!device)
32 throw eve::Exception("Could not open OpenAL device");
33 context = alcCreateContext(device, nullptr);
34 if (!context || !alcMakeContextCurrent(context))
35 throw eve::Exception("Could not create OpenAL context");
36 alListenerf(AL_GAIN, masterVolume);
37 worker = std::thread([this] { workerMain(); });
39}
40
43 running = false;
44 cv.notify_all();
45 if (worker.joinable())
46 worker.join();
47
48 {
49 std::lock_guard<std::mutex> lock(mutex);
50 // Sources are owned by callers; just clear tracking.
51 streamSources.clear();
52 allSources.clear();
53 }
54
55 alcMakeContextCurrent(nullptr);
56 if (context) {
57 alcDestroyContext(context);
58 context = nullptr;
59 }
60 if (device) {
61 alcCloseDevice(device);
62 device = nullptr;
63 }
64}
65
66void Audio::workerMain() {
67 while (running.load()) {
68 std::unique_lock<std::mutex> lock(mutex);
69 cv.wait_for(lock, std::chrono::milliseconds(10),
70 [this] { return !running.load() || !streamSources.empty(); });
71 if (!running.load())
72 break;
73 // Keep Audio::mutex held while filling. Source::~Source() removes the
74 // source from streamSources under the same mutex, so a source cannot be
75 // destroyed (and its decoderMutex/pendingMutex freed) while the worker
76 // is mid-fill. A snapshot-then-release pattern would let the destructor
77 // finish between the snapshot and the fill; the worker would then lock
78 // mutexes that no longer exist (std::system_error: mutex lock failed).
79 for (Source *s : streamSources) {
80 if (s)
81 s->fillPendingFromDecoder();
82 }
83 }
84}
85
87 std::lock_guard<std::mutex> lock(mutex);
88 streamSources.push_back(s);
89}
90
92 std::lock_guard<std::mutex> lock(mutex);
93 streamSources.erase(std::remove(streamSources.begin(), streamSources.end(), s), streamSources.end());
94 allSources.erase(std::remove(allSources.begin(), allSources.end(), s), allSources.end());
95}
96
100
101void Audio::notifyWorker() { cv.notify_all(); }
102
104 auto *s = new Source(this, data);
105 std::lock_guard<std::mutex> lock(mutex);
106 allSources.push_back(s);
107 return s;
108}
109
111 if (type != "static" && type != "stream")
112 throw eve::Exception("Invalid source type '%s'", type.c_str());
113 if (type == "static") {
114 auto *sound = sound::Sound::create();
115 auto *sd = sound->newSoundDataFromDecoder(decoder);
116 auto *s = newSource(sd);
117 // SoundData owned by caller eventually; keep sd alive via Source holding pointer only —
118 // Source does not own SoundData. Caller must keep SoundData alive.
119 // For static-from-decoder convenience, leak prevention: Source should own or we document.
120 // Spec: squirrel manages SoundData. Here C++ path: attach as dependency by not deleting.
121 // Store sd on Source via staticData without ownership — caller must delete sd after Source.
122 // Better: Source holds SoundData* without delete; document. For this factory, transfer:
123 // We'll let Source keep staticData pointer; user deletes Source then SoundData.
124 return s;
125 }
126 auto *s = new Source(this, decoder, true);
127 std::lock_guard<std::mutex> lock(mutex);
128 allSources.push_back(s);
129 return s;
130}
131
133 if (type != "static" && type != "stream")
134 throw eve::Exception("Invalid source type '%s'", type.c_str());
135 auto *sound = sound::Sound::create();
136 if (type == "static") {
137 auto *sd = sound->newSoundData(data);
138 return newSource(sd);
139 }
140 auto *dec = sound->newDecoder(data);
141 auto *s = new Source(this, dec, true, true);
142 std::lock_guard<std::mutex> lock(mutex);
143 allSources.push_back(s);
144 return s;
145}
146
148 if (s)
149 s->play();
150}
152 if (s)
153 s->stop();
154}
156 if (s)
157 s->pause();
158}
160 // Hold the mutex while stopping: Source::~Source() → unregisterSource()
161 // blocks on the same mutex, so a concurrently destroyed source cannot be
162 // touched here after it has been removed from allSources.
163 std::lock_guard<std::mutex> lock(mutex);
164 for (Source *s : allSources) {
165 if (s)
166 s->stop();
167 }
168}
169
171 std::lock_guard<std::mutex> lock(mutex);
172 return static_cast<int>(allSources.size());
173}
174
175void Audio::setVolume(float v) {
176 masterVolume = std::max(0.f, v);
177 alListenerf(AL_GAIN, masterVolume);
178}
179float Audio::getVolume() const { return masterVolume; }
180
181void Audio::setPosition(float x, float y, float z) { alListener3f(AL_POSITION, x, y, z); }
182void Audio::setVelocity(float x, float y, float z) { alListener3f(AL_VELOCITY, x, y, z); }
183void Audio::setOrientation(float fx, float fy, float fz, float ux, float uy, float uz) {
184 float ori[6] = {fx, fy, fz, ux, uy, uz};
185 alListenerfv(AL_ORIENTATION, ori);
186}
187
189 EV_PROFILE_MODULE("audio", "Audio::pump");
190 // Same guarantee as workerMain: destruction removes the source under this
191 // mutex, so the pointer stays valid for the whole iteration.
192 std::lock_guard<std::mutex> lock(mutex);
193 for (Source *s : streamSources) {
194 if (s)
195 s->pump();
196 }
197}
198
199void Audio::expose(ssq::Table &table) {
200 auto cls = table.addClass(name, Audio::create, false);
201 expose(cls);
202
203 auto src = table.addClass<Source>(
204 "Source", std::function<Source *()>([]() -> Source * { return nullptr; }), true);
205 src.addFunc("play", &Source::play);
206 src.addFunc("pause", &Source::pause);
207 src.addFunc("stop", &Source::stop);
208 src.addFunc("isPlaying", &Source::isPlaying);
209 src.addFunc("setVolume", &Source::setVolume);
210 src.addFunc("getVolume", &Source::getVolume);
211 src.addFunc("setPitch", &Source::setPitch);
212 src.addFunc("getPitch", &Source::getPitch);
213 src.addFunc("setLooping", &Source::setLooping);
214 src.addFunc("isLooping", &Source::isLooping);
215 src.addFunc("seek", &Source::seek);
216 src.addFunc("tell", &Source::tell);
217 src.addFunc("getDuration", &Source::getDuration);
218 src.addFunc("setPosition", &Source::setPosition);
219 src.addFunc("setVelocity", &Source::setVelocity);
220 src.addFunc("setDirection", &Source::setDirection);
221 src.addFunc("setRelative", &Source::setRelative);
222 src.addFunc("setAttenuationDistances", &Source::setAttenuationDistances);
223 table.addFunc("applyUnderwaterAudio",
224 [vm = table.getHandle()](Source* down, Source* up, Source* ambience,
225 bool playDown, bool playUp, bool loopAmbience,
226 float volume) {
227 return eve::script::projectResult(
228 vm, applyUnderwaterAudio(down, up, ambience, playDown, playUp, loopAmbience, volume));
229 });
230 auto zoneItem=table.addClass("AudioZoneItem",ssq::Class::Ctor<AudioZoneItem()>());
231 zoneItem.addVar("volume",&AudioZoneItem::volume); zoneItem.addVar("fadeInTime",&AudioZoneItem::fadeInTime);
232 zoneItem.addVar("fadeOutTime",&AudioZoneItem::fadeOutTime); zoneItem.addVar("duration",&AudioZoneItem::duration);
233 auto zoneProfile=table.addClass("AudioZoneProfile",ssq::Class::Ctor<AudioZoneProfile()>());
234 zoneProfile.addVar("x",&AudioZoneProfile::x); zoneProfile.addVar("y",&AudioZoneProfile::y);
235 zoneProfile.addVar("z",&AudioZoneProfile::z); zoneProfile.addVar("radius",&AudioZoneProfile::radius);
236 zoneProfile.addVar("minimumBreakTime",&AudioZoneProfile::minimumBreakTime);
237 zoneProfile.addVar("maximumBreakTime",&AudioZoneProfile::maximumBreakTime);
238 zoneProfile.addVar("deactivationTime",&AudioZoneProfile::deactivationTime);
239 zoneProfile.addVar("global",&AudioZoneProfile::global);
240 zoneProfile.addFunc("addItem",[vm=table.getHandle()](AudioZoneProfile* p,const AudioZoneItem* item){
241 auto r=p&&item?p->addItem(*item):Result<int>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"profile and item required"));
242 return eve::script::projectResult(vm,std::move(r),[](int v){return Value(v);});
243 });
244 zoneProfile.addFunc("getItemCount",[](const AudioZoneProfile* p){return p?p->itemCount():0;});
245 auto zoneState=table.addClass("AudioZoneState",ssq::Class::Ctor<AudioZoneState()>());
246 zoneState.addVar("selectedTrack",&AudioZoneState::selectedTrack); zoneState.addVar("playing",&AudioZoneState::playing);
247 table.addFunc("evaluateAudioZone",[vm=table.getHandle()](const AudioZoneProfile* p,AudioZoneState* state,
248 float now,float x,float y,float z,float master,std::uint32_t seed){
249 auto r=p&&state?evaluateAudioZone(*p,*state,now,x,y,z,master,seed)
250 :Result<AudioZoneOutput>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument,"profile and state required"));
251 return eve::script::projectResult(vm,std::move(r),[](const AudioZoneOutput& v){return Value::Object{
252 {"play",v.play},{"stop",v.stop},{"trackIndex",v.trackIndex},{"volume",v.volume}};});
253 });
254 table.addFunc("applyAudioZoneOutput",[vm=table.getHandle()](Source* source,bool play,bool stop,float volume){
255 AudioZoneOutput output; output.play=play; output.stop=stop; output.volume=volume;
257 });
258}
259
260void Audio::expose(ssq::Class &cls) {
261 cls.addFunc("getName", &Audio::getName);
262 cls.addFunc("newSource", &Audio::newSource);
263 cls.addFunc("newSourceFromDecoder", &Audio::newSourceFromDecoder);
264 cls.addFunc("newSourceFromData", &Audio::newSourceFromData);
265 cls.addFunc("play", &Audio::play);
266 cls.addFunc("stop", &Audio::stop);
267 cls.addFunc("stopAll", &Audio::stopAll);
268 cls.addFunc("pause", &Audio::pause);
269 cls.addFunc("setVolume", &Audio::setVolume);
270 cls.addFunc("getVolume", &Audio::getVolume);
271 cls.addFunc("setPosition", &Audio::setPosition);
272 cls.addFunc("setVelocity", &Audio::setVelocity);
273 cls.addFunc("setOrientation", &Audio::setOrientation);
274}
275
276} // namespace audio
277} // namespace eve
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string output
OwnedAudio audio
double volume
const std::string & s
glm::vec4 p[6]
HSQUIRRELVM vm
Definition ECS.cpp:20
HSQOBJECT cls
Definition ECS.cpp:21
double r
float v
HexVec3 up
std::string name
#define Module_IMPL(ModuleName, newExpr)
Definition Module.h:26
std::string error
Definition Package.cpp:60
std::uint32_t seed
Definition PointSet.cpp:807
#define EV_PROFILE_MODULE(module, name)
Profile the enclosing scope, tagged with a module for grouping.
Definition Profile.h:140
The single Squirrel projection for common Result, Status and Value.
const UnitySourceAsset & source
Data public API.
Definition Data.h:8
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
StartupStage public API.
std::map< std::string, Value > Object
Definition Value.h:34
OpenAL audio module: device management, master listener state, and Source factory....
Definition Audio.h:29
void pause(Source *s)
Pauses a source (no-op when s is null).
Definition Audio.cpp:155
Source * newSourceFromDecoder(sound::Decoder *decoder, std::string type)
Creates a Source from a decoder; type must be "static" or "stream".
Definition Audio.cpp:110
float getVolume() const
Returns the volume.
Definition Audio.cpp:179
void unregisterSource(Source *s)
Internal: removes a source from all module tracking.
Definition Audio.cpp:91
void setVolume(float v)
Sets master volume (clamped to >= 0) applied to the OpenAL listener.
Definition Audio.cpp:175
void stop(Source *s)
Stops playback of a source (no-op when s is null).
Definition Audio.cpp:151
void setVelocity(float x, float y, float z)
Sets the listener velocity (used by OpenAL doppler).
Definition Audio.cpp:182
void stopAll()
Stops every live source registered with this module.
Definition Audio.cpp:159
void setPosition(float x, float y, float z)
Sets the listener position in world units.
Definition Audio.cpp:181
Source * newSourceFromData(Data *data, std::string type)
Creates a Source from raw encoded data; type must be "static" or "stream".
Definition Audio.cpp:132
Source * newSource(sound::SoundData *data)
Creates a static (non-streaming) Source from decoded sound data.
Definition Audio.cpp:103
void play(Source *s)
Starts playback of a source (no-op when s is null).
Definition Audio.cpp:147
~Audio() override
Audio.
Definition Audio.cpp:41
void registerStream(Source *s)
Internal: registers a streaming source with the decode worker.
Definition Audio.cpp:86
void unregisterStream(Source *s)
Internal: removes a source from worker tracking.
Definition Audio.cpp:97
int getSourceCount()
Return the current number of caller- or handler-owned live sources.
Definition Audio.cpp:170
Audio()
Audio.
Definition Audio.cpp:27
void setOrientation(float fx, float fy, float fz, float ux, float uy, float uz)
Sets the listener forward and up orientation vectors.
Definition Audio.cpp:183
void pump()
Advances streaming sources; call once per frame from the main thread.
Definition Audio.cpp:188
void notifyWorker()
Internal: wakes the decode worker thread.
Definition Audio.cpp:101
A playable audio source (static buffer or streaming decoder). Not thread-safe for playback control ex...
Definition Source.h:30
void play()
Starts (or resumes) playback.
Definition Source.cpp:120
void pause()
Pauses playback, keeping the play position.
Definition Source.cpp:135
void setVolume(float v)
Sets source gain (clamped to >= 0).
Definition Source.cpp:181
float getVolume() const
Returns the volume.
Definition Source.cpp:185
void setVelocity(float x, float y, float z)
Sets the source velocity (used by OpenAL doppler).
Definition Source.cpp:239
bool isPlaying() const
True when playing.
Definition Source.cpp:175
float getPitch() const
Returns the pitch.
Definition Source.cpp:191
void setRelative(bool relative)
Makes the source ignore the listener position (head-relative).
Definition Source.cpp:241
void setPosition(float x, float y, float z)
Sets the source position in world units.
Definition Source.cpp:238
void stop()
Stops playback and rewinds the play position.
Definition Source.cpp:140
double getDuration() const
Total duration in seconds (0 for live/unbounded streams).
Definition Source.cpp:229
double tell() const
Current play time in seconds.
Definition Source.cpp:223
void setAttenuationDistances(float ref, float max)
Sets OpenAL reference and maximum attenuation distances.
Definition Source.cpp:244
bool seek(double seconds)
Seeks to a time in seconds; false when seeking is unsupported.
Definition Source.cpp:200
void setLooping(bool l)
Enables/disables looping.
Definition Source.cpp:193
void setDirection(float x, float y, float z)
Sets the source directional cone orientation.
Definition Source.cpp:240
bool isLooping() const
True when looping.
Definition Source.cpp:198
void setPitch(float p)
Sets playback pitch (clamped to >= 0).
Definition Source.cpp:187
Streaming audio decoder (medialoader-backed). Owns the raw encoded bytes and decodes incrementally vi...
Definition Decoder.h:22
Raw PCM audio buffer with format metadata (samples/rate/bit depth/channels).
Definition SoundData.h:15
void bindAudioEventSinkOwner(Audio *owner) noexcept
Bind the live Audio module into the platform event pump sink. @ownership Borrowed; cleared when the A...
Definition EventSink.cpp:35
Result< AudioZoneOutput > evaluateAudioZone(const AudioZoneProfile &p, AudioZoneState &state, float now, float px, float py, float pz, float master, std::uint32_t seed)
Evaluate PcgAudioManager/PcgAudioZone for one explicit time and player observation.
Definition AudioZone.cpp:21
void registerAudioCapabilities()
Registers audio capabilities.
Result< void > applyAudioZoneOutput(Source *source, const AudioZoneOutput &output)
Apply one evaluated command to the caller-selected track Source without retaining it.
Definition AudioZone.cpp:59
eve::Diagnostic Diagnostic
eve::Result< T > Result
std::unordered_map< std::string, SkillDefinition > & table()
Definition Skill.cpp:65
ssq::Table projectResult(HSQUIRRELVM vm, Result< void > &&result)
Consume and project a void native Result using the common schema.
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
DiagnosticCode
Stable machine-readable diagnostic codes.
Definition Diagnostic.h:47