CubebUtils.cpp 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* vim:set ts=2 sw=2 sts=2 et cindent: */
  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/. */
  6. #include <stdint.h>
  7. #include <algorithm>
  8. #include "nsIStringBundle.h"
  9. #include "nsDebug.h"
  10. #include "nsString.h"
  11. #include "mozilla/Preferences.h"
  12. #include "mozilla/Services.h"
  13. #include "mozilla/Sprintf.h"
  14. #include "mozilla/StaticMutex.h"
  15. #include "mozilla/StaticPtr.h"
  16. #include "mozilla/Telemetry.h"
  17. #include "mozilla/Logging.h"
  18. #include "nsThreadUtils.h"
  19. #include "CubebUtils.h"
  20. #include "nsAutoRef.h"
  21. #include "prdtoa.h"
  22. #define PREF_VOLUME_SCALE "media.volume_scale"
  23. #define PREF_CUBEB_LATENCY_PLAYBACK "media.cubeb_latency_playback_ms"
  24. #define PREF_CUBEB_LATENCY_MSG "media.cubeb_latency_msg_frames"
  25. namespace mozilla {
  26. namespace {
  27. LazyLogModule gCubebLog("cubeb");
  28. void CubebLogCallback(const char* aFmt, ...)
  29. {
  30. char buffer[256];
  31. va_list arglist;
  32. va_start(arglist, aFmt);
  33. VsprintfLiteral (buffer, aFmt, arglist);
  34. MOZ_LOG(gCubebLog, LogLevel::Error, ("%s", buffer));
  35. va_end(arglist);
  36. }
  37. // This mutex protects the variables below.
  38. StaticMutex sMutex;
  39. enum class CubebState {
  40. Uninitialized = 0,
  41. Initialized,
  42. Shutdown
  43. } sCubebState = CubebState::Uninitialized;
  44. cubeb* sCubebContext;
  45. double sVolumeScale;
  46. uint32_t sCubebPlaybackLatencyInMilliseconds;
  47. uint32_t sCubebMSGLatencyInFrames;
  48. bool sCubebPlaybackLatencyPrefSet;
  49. bool sCubebMSGLatencyPrefSet;
  50. bool sAudioStreamInitEverSucceeded = false;
  51. StaticAutoPtr<char> sBrandName;
  52. const char kBrandBundleURL[] = "chrome://branding/locale/brand.properties";
  53. const char* AUDIOSTREAM_BACKEND_ID_STR[] = {
  54. "jack",
  55. "pulse",
  56. "alsa",
  57. "audiounit",
  58. "audioqueue",
  59. "wasapi",
  60. "winmm",
  61. "directsound",
  62. "sndio",
  63. "opensl",
  64. "audiotrack",
  65. "kai"
  66. };
  67. /* Index for failures to create an audio stream the first time. */
  68. const int CUBEB_BACKEND_INIT_FAILURE_FIRST =
  69. ArrayLength(AUDIOSTREAM_BACKEND_ID_STR);
  70. /* Index for failures to create an audio stream after the first time */
  71. const int CUBEB_BACKEND_INIT_FAILURE_OTHER = CUBEB_BACKEND_INIT_FAILURE_FIRST + 1;
  72. /* Index for an unknown backend. */
  73. const int CUBEB_BACKEND_UNKNOWN = CUBEB_BACKEND_INIT_FAILURE_FIRST + 2;
  74. // Prefered samplerate, in Hz (characteristic of the hardware, mixer, platform,
  75. // and API used).
  76. //
  77. // sMutex protects *initialization* of this, which must be performed from each
  78. // thread before fetching, after which it is safe to fetch without holding the
  79. // mutex because it is only written once per process execution (by the first
  80. // initialization to complete). Since the init must have been called on a
  81. // given thread before fetching the value, it's guaranteed (via the mutex) that
  82. // sufficient memory barriers have occurred to ensure the correct value is
  83. // visible on the querying thread/CPU.
  84. uint32_t sPreferredSampleRate;
  85. } // namespace
  86. extern LazyLogModule gAudioStreamLog;
  87. static const uint32_t CUBEB_NORMAL_LATENCY_MS = 100;
  88. // Consevative default that can work on all platforms.
  89. static const uint32_t CUBEB_NORMAL_LATENCY_FRAMES = 1024;
  90. namespace CubebUtils {
  91. void PrefChanged(const char* aPref, void* aClosure)
  92. {
  93. if (strcmp(aPref, PREF_VOLUME_SCALE) == 0) {
  94. nsAdoptingString value = Preferences::GetString(aPref);
  95. StaticMutexAutoLock lock(sMutex);
  96. if (value.IsEmpty()) {
  97. sVolumeScale = 1.0;
  98. } else {
  99. NS_ConvertUTF16toUTF8 utf8(value);
  100. sVolumeScale = std::max<double>(0, PR_strtod(utf8.get(), nullptr));
  101. }
  102. } else if (strcmp(aPref, PREF_CUBEB_LATENCY_PLAYBACK) == 0) {
  103. // Arbitrary default stream latency of 100ms. The higher this
  104. // value, the longer stream volume changes will take to become
  105. // audible.
  106. sCubebPlaybackLatencyPrefSet = Preferences::HasUserValue(aPref);
  107. uint32_t value = Preferences::GetUint(aPref, CUBEB_NORMAL_LATENCY_MS);
  108. StaticMutexAutoLock lock(sMutex);
  109. sCubebPlaybackLatencyInMilliseconds = std::min<uint32_t>(std::max<uint32_t>(value, 1), 1000);
  110. } else if (strcmp(aPref, PREF_CUBEB_LATENCY_MSG) == 0) {
  111. sCubebMSGLatencyPrefSet = Preferences::HasUserValue(aPref);
  112. uint32_t value = Preferences::GetUint(aPref, CUBEB_NORMAL_LATENCY_FRAMES);
  113. StaticMutexAutoLock lock(sMutex);
  114. // 128 is the block size for the Web Audio API, which limits how low the
  115. // latency can be here.
  116. // We don't want to limit the upper limit too much, so that people can
  117. // experiment.
  118. sCubebMSGLatencyInFrames = std::min<uint32_t>(std::max<uint32_t>(value, 128), 1e6);
  119. }
  120. }
  121. bool GetFirstStream()
  122. {
  123. static bool sFirstStream = true;
  124. StaticMutexAutoLock lock(sMutex);
  125. bool result = sFirstStream;
  126. sFirstStream = false;
  127. return result;
  128. }
  129. double GetVolumeScale()
  130. {
  131. StaticMutexAutoLock lock(sMutex);
  132. return sVolumeScale;
  133. }
  134. cubeb* GetCubebContext()
  135. {
  136. StaticMutexAutoLock lock(sMutex);
  137. return GetCubebContextUnlocked();
  138. }
  139. bool InitPreferredSampleRate()
  140. {
  141. StaticMutexAutoLock lock(sMutex);
  142. if (sPreferredSampleRate != 0) {
  143. return true;
  144. }
  145. cubeb* context = GetCubebContextUnlocked();
  146. if (!context) {
  147. return false;
  148. }
  149. if (cubeb_get_preferred_sample_rate(context,
  150. &sPreferredSampleRate) != CUBEB_OK) {
  151. return false;
  152. }
  153. MOZ_ASSERT(sPreferredSampleRate);
  154. return true;
  155. }
  156. uint32_t PreferredSampleRate()
  157. {
  158. if (!InitPreferredSampleRate()) {
  159. return 44100;
  160. }
  161. MOZ_ASSERT(sPreferredSampleRate);
  162. return sPreferredSampleRate;
  163. }
  164. void InitBrandName()
  165. {
  166. if (sBrandName) {
  167. return;
  168. }
  169. nsXPIDLString brandName;
  170. nsCOMPtr<nsIStringBundleService> stringBundleService =
  171. mozilla::services::GetStringBundleService();
  172. if (stringBundleService) {
  173. nsCOMPtr<nsIStringBundle> brandBundle;
  174. nsresult rv = stringBundleService->CreateBundle(kBrandBundleURL,
  175. getter_AddRefs(brandBundle));
  176. if (NS_SUCCEEDED(rv)) {
  177. rv = brandBundle->GetStringFromName(u"brandShortName",
  178. getter_Copies(brandName));
  179. NS_WARNING_ASSERTION(
  180. NS_SUCCEEDED(rv), "Could not get the program name for a cubeb stream.");
  181. }
  182. }
  183. NS_LossyConvertUTF16toASCII ascii(brandName);
  184. sBrandName = new char[ascii.Length() + 1];
  185. PodCopy(sBrandName.get(), ascii.get(), ascii.Length());
  186. sBrandName[ascii.Length()] = 0;
  187. }
  188. cubeb* GetCubebContextUnlocked()
  189. {
  190. sMutex.AssertCurrentThreadOwns();
  191. if (sCubebState != CubebState::Uninitialized) {
  192. // If we have already passed the initialization point (below), just return
  193. // the current context, which may be null (e.g., after error or shutdown.)
  194. return sCubebContext;
  195. }
  196. if (!sBrandName && NS_IsMainThread()) {
  197. InitBrandName();
  198. } else {
  199. NS_WARNING_ASSERTION(
  200. sBrandName, "Did not initialize sbrandName, and not on the main thread?");
  201. }
  202. int rv = cubeb_init(&sCubebContext, sBrandName);
  203. NS_WARNING_ASSERTION(rv == CUBEB_OK, "Could not get a cubeb context.");
  204. sCubebState = (rv == CUBEB_OK) ? CubebState::Initialized : CubebState::Uninitialized;
  205. if (MOZ_LOG_TEST(gCubebLog, LogLevel::Verbose)) {
  206. cubeb_set_log_callback(CUBEB_LOG_VERBOSE, CubebLogCallback);
  207. } else if (MOZ_LOG_TEST(gCubebLog, LogLevel::Error)) {
  208. cubeb_set_log_callback(CUBEB_LOG_NORMAL, CubebLogCallback);
  209. }
  210. return sCubebContext;
  211. }
  212. uint32_t GetCubebPlaybackLatencyInMilliseconds()
  213. {
  214. StaticMutexAutoLock lock(sMutex);
  215. return sCubebPlaybackLatencyInMilliseconds;
  216. }
  217. bool CubebPlaybackLatencyPrefSet()
  218. {
  219. StaticMutexAutoLock lock(sMutex);
  220. return sCubebPlaybackLatencyPrefSet;
  221. }
  222. bool CubebMSGLatencyPrefSet()
  223. {
  224. StaticMutexAutoLock lock(sMutex);
  225. return sCubebMSGLatencyPrefSet;
  226. }
  227. Maybe<uint32_t> GetCubebMSGLatencyInFrames()
  228. {
  229. StaticMutexAutoLock lock(sMutex);
  230. if (!sCubebMSGLatencyPrefSet) {
  231. return Maybe<uint32_t>();
  232. }
  233. MOZ_ASSERT(sCubebMSGLatencyInFrames > 0);
  234. return Some(sCubebMSGLatencyInFrames);
  235. }
  236. void InitLibrary()
  237. {
  238. PrefChanged(PREF_VOLUME_SCALE, nullptr);
  239. Preferences::RegisterCallback(PrefChanged, PREF_VOLUME_SCALE);
  240. PrefChanged(PREF_CUBEB_LATENCY_PLAYBACK, nullptr);
  241. PrefChanged(PREF_CUBEB_LATENCY_MSG, nullptr);
  242. Preferences::RegisterCallback(PrefChanged, PREF_CUBEB_LATENCY_PLAYBACK);
  243. Preferences::RegisterCallback(PrefChanged, PREF_CUBEB_LATENCY_MSG);
  244. NS_DispatchToMainThread(NS_NewRunnableFunction(&InitBrandName));
  245. }
  246. void ShutdownLibrary()
  247. {
  248. Preferences::UnregisterCallback(PrefChanged, PREF_VOLUME_SCALE);
  249. Preferences::UnregisterCallback(PrefChanged, PREF_CUBEB_LATENCY_PLAYBACK);
  250. Preferences::UnregisterCallback(PrefChanged, PREF_CUBEB_LATENCY_MSG);
  251. StaticMutexAutoLock lock(sMutex);
  252. if (sCubebContext) {
  253. cubeb_destroy(sCubebContext);
  254. sCubebContext = nullptr;
  255. }
  256. sBrandName = nullptr;
  257. // This will ensure we don't try to re-create a context.
  258. sCubebState = CubebState::Shutdown;
  259. }
  260. uint32_t MaxNumberOfChannels()
  261. {
  262. cubeb* cubebContext = GetCubebContext();
  263. uint32_t maxNumberOfChannels;
  264. if (cubebContext &&
  265. cubeb_get_max_channel_count(cubebContext,
  266. &maxNumberOfChannels) == CUBEB_OK) {
  267. return maxNumberOfChannels;
  268. }
  269. return 0;
  270. }
  271. void GetCurrentBackend(nsAString& aBackend)
  272. {
  273. cubeb* cubebContext = GetCubebContext();
  274. if (cubebContext) {
  275. const char* backend = cubeb_get_backend_id(cubebContext);
  276. if (backend) {
  277. aBackend.AssignASCII(backend);
  278. return;
  279. }
  280. }
  281. aBackend.AssignLiteral("unknown");
  282. }
  283. } // namespace CubebUtils
  284. } // namespace mozilla