Bug 1861709 replace AudioCallbackDriver::ThreadRunning() assertions that mean to...
[gecko.git] / widget / windows / AudioSession.cpp
bloba38e2a83f6de553590a192f481bc29082a4fba3f
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include <atomic>
8 #include <audiopolicy.h>
9 #include <windows.h>
10 #include <mmdeviceapi.h>
12 #include "mozilla/ClearOnShutdown.h"
13 #include "mozilla/RefPtr.h"
14 #include "mozilla/ScopeExit.h"
15 #include "mozilla/StaticPtr.h"
16 #include "nsIStringBundle.h"
18 #include "nsCOMPtr.h"
19 #include "nsID.h"
20 #include "nsServiceManagerUtils.h"
21 #include "nsString.h"
22 #include "nsThreadUtils.h"
23 #include "nsXULAppAPI.h"
24 #include "mozilla/Attributes.h"
25 #include "mozilla/mscom/AgileReference.h"
26 #include "mozilla/mscom/Utils.h"
27 #include "mozilla/Mutex.h"
28 #include "mozilla/WindowsVersion.h"
30 namespace mozilla {
31 namespace widget {
34 * To take advantage of what Vista+ have to offer with respect to audio,
35 * we need to maintain an audio session. This class wraps IAudioSessionControl
36 * and implements IAudioSessionEvents (for callbacks from Windows)
38 class AudioSession final : public IAudioSessionEvents {
39 public:
40 AudioSession();
42 static AudioSession* GetSingleton();
44 // COM IUnknown
45 STDMETHODIMP_(ULONG) AddRef();
46 STDMETHODIMP QueryInterface(REFIID, void**);
47 STDMETHODIMP_(ULONG) Release();
49 // IAudioSessionEvents
50 STDMETHODIMP OnChannelVolumeChanged(DWORD aChannelCount,
51 float aChannelVolumeArray[],
52 DWORD aChangedChannel, LPCGUID aContext);
53 STDMETHODIMP OnDisplayNameChanged(LPCWSTR aDisplayName, LPCGUID aContext);
54 STDMETHODIMP OnGroupingParamChanged(LPCGUID aGroupingParam, LPCGUID aContext);
55 STDMETHODIMP OnIconPathChanged(LPCWSTR aIconPath, LPCGUID aContext);
56 STDMETHODIMP OnSessionDisconnected(AudioSessionDisconnectReason aReason);
57 STDMETHODIMP OnSimpleVolumeChanged(float aVolume, BOOL aMute,
58 LPCGUID aContext);
59 STDMETHODIMP OnStateChanged(AudioSessionState aState);
61 void Start();
62 void Stop(bool shouldRestart = false);
64 nsresult GetSessionData(nsID& aID, nsString& aSessionName,
65 nsString& aIconPath);
66 nsresult SetSessionData(const nsID& aID, const nsString& aSessionName,
67 const nsString& aIconPath);
69 private:
70 ~AudioSession() = default;
72 void StopInternal(const MutexAutoLock& aProofOfLock,
73 bool shouldRestart = false);
75 protected:
76 RefPtr<IAudioSessionControl> mAudioSessionControl;
77 nsString mDisplayName;
78 nsString mIconPath;
79 nsID mSessionGroupingParameter;
80 // Guards the IAudioSessionControl
81 mozilla::Mutex mMutex MOZ_UNANNOTATED;
83 ThreadSafeAutoRefCnt mRefCnt;
84 NS_DECL_OWNINGTHREAD
87 StaticRefPtr<AudioSession> sService;
89 void StartAudioSession() {
90 MOZ_ASSERT(NS_IsMainThread());
91 MOZ_ASSERT(!sService);
92 sService = new AudioSession();
94 // Destroy AudioSession only after any background task threads have been
95 // stopped or abandoned.
96 ClearOnShutdown(&sService, ShutdownPhase::XPCOMShutdownFinal);
98 NS_DispatchBackgroundTask(
99 NS_NewCancelableRunnableFunction("StartAudioSession", []() -> void {
100 MOZ_ASSERT(AudioSession::GetSingleton(),
101 "AudioSession should outlive background threads");
102 AudioSession::GetSingleton()->Start();
103 }));
106 void StopAudioSession() {
107 MOZ_ASSERT(NS_IsMainThread());
108 MOZ_ASSERT(sService);
109 NS_DispatchBackgroundTask(
110 NS_NewRunnableFunction("StopAudioSession", []() -> void {
111 MOZ_ASSERT(AudioSession::GetSingleton(),
112 "AudioSession should outlive background threads");
113 AudioSession::GetSingleton()->Stop();
114 }));
117 AudioSession* AudioSession::GetSingleton() {
118 MOZ_ASSERT(mscom::IsCurrentThreadMTA());
119 return sService;
122 // It appears Windows will use us on a background thread ...
123 NS_IMPL_ADDREF(AudioSession)
124 NS_IMPL_RELEASE(AudioSession)
126 STDMETHODIMP
127 AudioSession::QueryInterface(REFIID iid, void** ppv) {
128 const IID IID_IAudioSessionEvents = __uuidof(IAudioSessionEvents);
129 if ((IID_IUnknown == iid) || (IID_IAudioSessionEvents == iid)) {
130 *ppv = static_cast<IAudioSessionEvents*>(this);
131 AddRef();
132 return S_OK;
135 return E_NOINTERFACE;
138 AudioSession::AudioSession() : mMutex("AudioSessionControl") {
139 // This func must be run on the main thread as
140 // nsStringBundle is not thread safe otherwise
141 MOZ_ASSERT(NS_IsMainThread());
143 MOZ_ASSERT(XRE_IsParentProcess(),
144 "Should only get here in a chrome process!");
146 nsCOMPtr<nsIStringBundleService> bundleService =
147 do_GetService(NS_STRINGBUNDLE_CONTRACTID);
148 MOZ_ASSERT(bundleService);
150 nsCOMPtr<nsIStringBundle> bundle;
151 bundleService->CreateBundle("chrome://branding/locale/brand.properties",
152 getter_AddRefs(bundle));
153 MOZ_ASSERT(bundle);
154 bundle->GetStringFromName("brandFullName", mDisplayName);
156 wchar_t* buffer;
157 mIconPath.GetMutableData(&buffer, MAX_PATH);
158 ::GetModuleFileNameW(nullptr, buffer, MAX_PATH);
160 [[maybe_unused]] nsresult rv =
161 nsID::GenerateUUIDInPlace(mSessionGroupingParameter);
162 MOZ_ASSERT(rv == NS_OK);
165 // Once we are started Windows will hold a reference to us through our
166 // IAudioSessionEvents interface that will keep us alive until the appshell
167 // calls Stop.
168 void AudioSession::Start() {
169 MOZ_ASSERT(mscom::IsCurrentThreadMTA());
171 const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
172 const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
173 const IID IID_IAudioSessionManager = __uuidof(IAudioSessionManager);
175 MutexAutoLock lock(mMutex);
176 MOZ_ASSERT(!mAudioSessionControl);
177 MOZ_ASSERT(!mDisplayName.IsEmpty() || !mIconPath.IsEmpty(),
178 "Should never happen ...");
180 auto scopeExit = MakeScopeExit([&] { StopInternal(lock); });
182 RefPtr<IMMDeviceEnumerator> enumerator;
183 HRESULT hr =
184 ::CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL,
185 IID_IMMDeviceEnumerator, getter_AddRefs(enumerator));
186 if (FAILED(hr)) {
187 return;
190 RefPtr<IMMDevice> device;
191 hr = enumerator->GetDefaultAudioEndpoint(
192 EDataFlow::eRender, ERole::eMultimedia, getter_AddRefs(device));
193 if (FAILED(hr)) {
194 return;
197 RefPtr<IAudioSessionManager> manager;
198 hr = device->Activate(IID_IAudioSessionManager, CLSCTX_ALL, nullptr,
199 getter_AddRefs(manager));
200 if (FAILED(hr)) {
201 return;
204 hr = manager->GetAudioSessionControl(&GUID_NULL, 0,
205 getter_AddRefs(mAudioSessionControl));
207 if (FAILED(hr) || !mAudioSessionControl) {
208 return;
211 // Increments refcount of 'this'.
212 hr = mAudioSessionControl->RegisterAudioSessionNotification(this);
213 if (FAILED(hr)) {
214 return;
217 hr = mAudioSessionControl->SetGroupingParam(
218 (LPGUID) & (mSessionGroupingParameter), nullptr);
219 if (FAILED(hr)) {
220 return;
223 hr = mAudioSessionControl->SetDisplayName(mDisplayName.get(), nullptr);
224 if (FAILED(hr)) {
225 return;
228 hr = mAudioSessionControl->SetIconPath(mIconPath.get(), nullptr);
229 if (FAILED(hr)) {
230 return;
233 scopeExit.release();
236 void AudioSession::Stop(bool shouldRestart) {
237 MOZ_ASSERT(mscom::IsCurrentThreadMTA());
239 MutexAutoLock lock(mMutex);
240 StopInternal(lock, shouldRestart);
243 void AudioSession::StopInternal(const MutexAutoLock& aProofOfLock,
244 bool shouldRestart) {
245 if (!mAudioSessionControl) {
246 return;
249 // Decrement refcount of 'this'
250 mAudioSessionControl->UnregisterAudioSessionNotification(this);
252 // Deleting the IAudioSessionControl COM object requires the STA/main thread.
253 // Audio code may concurrently be running on the main thread and it may
254 // block waiting for this to complete, creating deadlock. So we destroy the
255 // IAudioSessionControl on the main thread instead. In order to do that, we
256 // need to marshall the object to the main thread's apartment with an
257 // AgileReference.
258 const IID IID_IAudioSessionControl = __uuidof(IAudioSessionControl);
259 auto agileAsc = MakeUnique<mozilla::mscom::AgileReference>(
260 IID_IAudioSessionControl, mAudioSessionControl);
261 mAudioSessionControl = nullptr;
262 NS_DispatchToMainThread(NS_NewRunnableFunction(
263 "FreeAudioSession", [agileAsc = std::move(agileAsc),
264 IID_IAudioSessionControl, shouldRestart] {
265 RefPtr<IAudioSessionControl> toDelete;
266 [[maybe_unused]] HRESULT hr = agileAsc->Resolve(
267 IID_IAudioSessionControl, getter_AddRefs(toDelete));
268 MOZ_ASSERT(SUCCEEDED(hr));
269 // Now release the AgileReference which holds our only reference to the
270 // IAudioSessionControl, then maybe restart.
271 if (shouldRestart) {
272 NS_DispatchBackgroundTask(
273 NS_NewCancelableRunnableFunction("RestartAudioSession", [] {
274 AudioSession* as = AudioSession::GetSingleton();
275 MOZ_ASSERT(as);
276 as->Start();
277 }));
279 }));
282 void CopynsID(nsID& lhs, const nsID& rhs) {
283 lhs.m0 = rhs.m0;
284 lhs.m1 = rhs.m1;
285 lhs.m2 = rhs.m2;
286 for (int i = 0; i < 8; i++) {
287 lhs.m3[i] = rhs.m3[i];
291 nsresult AudioSession::GetSessionData(nsID& aID, nsString& aSessionName,
292 nsString& aIconPath) {
293 CopynsID(aID, mSessionGroupingParameter);
294 aSessionName = mDisplayName;
295 aIconPath = mIconPath;
297 return NS_OK;
300 nsresult AudioSession::SetSessionData(const nsID& aID,
301 const nsString& aSessionName,
302 const nsString& aIconPath) {
303 MOZ_ASSERT(!XRE_IsParentProcess(),
304 "Should never get here in a chrome process!");
305 CopynsID(mSessionGroupingParameter, aID);
306 mDisplayName = aSessionName;
307 mIconPath = aIconPath;
308 return NS_OK;
311 STDMETHODIMP
312 AudioSession::OnChannelVolumeChanged(DWORD aChannelCount,
313 float aChannelVolumeArray[],
314 DWORD aChangedChannel, LPCGUID aContext) {
315 return S_OK; // NOOP
318 STDMETHODIMP
319 AudioSession::OnDisplayNameChanged(LPCWSTR aDisplayName, LPCGUID aContext) {
320 return S_OK; // NOOP
323 STDMETHODIMP
324 AudioSession::OnGroupingParamChanged(LPCGUID aGroupingParam, LPCGUID aContext) {
325 return S_OK; // NOOP
328 STDMETHODIMP
329 AudioSession::OnIconPathChanged(LPCWSTR aIconPath, LPCGUID aContext) {
330 return S_OK; // NOOP
333 STDMETHODIMP
334 AudioSession::OnSessionDisconnected(AudioSessionDisconnectReason aReason) {
335 Stop(true /* shouldRestart */);
336 return S_OK;
339 STDMETHODIMP
340 AudioSession::OnSimpleVolumeChanged(float aVolume, BOOL aMute,
341 LPCGUID aContext) {
342 return S_OK; // NOOP
345 STDMETHODIMP
346 AudioSession::OnStateChanged(AudioSessionState aState) {
347 return S_OK; // NOOP
350 } // namespace widget
351 } // namespace mozilla