llstreamingaudio_fmod.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. /**
  2. * @file llstreamingaudio_fmod.cpp
  3. * @brief LLStreamingAudio_FMOD implementation
  4. *
  5. * $LicenseInfo:firstyear=2009&license=viewergpl$
  6. *
  7. * Copyright (c) 2009, Linden Research, Inc.
  8. *
  9. * Second Life Viewer Source Code
  10. * The source code in this file ("Source Code") is provided by Linden Lab
  11. * to you under the terms of the GNU General Public License, version 2.0
  12. * ("GPL"), unless you have obtained a separate licensing agreement
  13. * ("Other License"), formally executed by you and Linden Lab. Terms of
  14. * the GPL can be found in doc/GPL-license.txt in this distribution, or
  15. * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  16. *
  17. * There are special exceptions to the terms and conditions of the GPL as
  18. * it is applied to this Source Code. View the full text of the exception
  19. * in the file doc/FLOSS-exception.txt in this software distribution, or
  20. * online at
  21. * http://secondlifegrid.net/programs/open_source/licensing/flossexception
  22. *
  23. * By copying, modifying or distributing this software, you acknowledge
  24. * that you have read and understood your obligations described above,
  25. * and agree to abide by those obligations.
  26. *
  27. * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  28. * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  29. * COMPLETENESS OR PERFORMANCE.
  30. * $/LicenseInfo$
  31. */
  32. #include "linden_common.h"
  33. #include "fmod.hpp"
  34. #include "fmod_errors.h"
  35. #include "llstreamingaudio_fmod.h"
  36. #include "llmath.h"
  37. #include "llstring.h"
  38. #include "lltimer.h"
  39. #include "llaudioengine_fmod.h" // For checkFMerr()
  40. constexpr U32 ESTIMATED_BIT_RATE = 128; // in kbps
  41. constexpr U32 BYTES_PER_KBIT = 1024 / 8;
  42. // Seconds before force-releasing streams:
  43. constexpr F32 FORCE_RELEASE_DELAY = 3.f;
  44. //---------------------------------------------------------------------------
  45. // LLAudioStreamManagerFMOD implementation
  46. // Manager of possibly-multiple Internet audio streams
  47. //---------------------------------------------------------------------------
  48. class LLAudioStreamManagerFMOD
  49. {
  50. public:
  51. LLAudioStreamManagerFMOD(FMOD::System* system, FMOD::ChannelGroup* group,
  52. const std::string& url);
  53. FMOD::Channel* startStream();
  54. // Returns true if the stream was successfully stopped:
  55. bool releaseStream(bool force = false);
  56. bool ready();
  57. LL_INLINE const std::string& getURL() { return mInternetStreamURL; }
  58. FMOD_OPENSTATE getOpenState(unsigned int* pctbuffered = NULL,
  59. bool* starving = NULL, bool* diskbusy = NULL);
  60. protected:
  61. FMOD::System* mSystem;
  62. FMOD::ChannelGroup* mChannelGroup;
  63. FMOD::Channel* mStreamChannel;
  64. FMOD::Sound* mInternetStream;
  65. std::string mInternetStreamURL;
  66. F32 mFirstReleaseAttempt;
  67. bool mReady;
  68. };
  69. LLAudioStreamManagerFMOD::LLAudioStreamManagerFMOD(FMOD::System* system,
  70. FMOD::ChannelGroup* group,
  71. const std::string& url)
  72. : mSystem(system),
  73. mChannelGroup(group),
  74. mStreamChannel(NULL),
  75. mInternetStream(NULL),
  76. mFirstReleaseAttempt(0.f),
  77. mReady(false)
  78. {
  79. mInternetStreamURL = url;
  80. constexpr FMOD_MODE mode = FMOD_2D | FMOD_NONBLOCKING | FMOD_IGNORETAGS;
  81. FMOD_RESULT result = mSystem->createStream(url.c_str(), mode, NULL,
  82. &mInternetStream);
  83. mReady = result == FMOD_OK;
  84. if (!mReady || !mInternetStream)
  85. {
  86. llwarns << "Could not open fmod stream " << url << " - Error: "
  87. << FMOD_ErrorString(result) << llendl;
  88. }
  89. }
  90. FMOD::Channel* LLAudioStreamManagerFMOD::startStream()
  91. {
  92. if (!mSystem) // Paranoia
  93. {
  94. llwarns << "mSystem is NULL !" << llendl;
  95. return NULL;
  96. }
  97. // We need a live and opened stream before we try and play it.
  98. if (!mInternetStream || getOpenState() != FMOD_OPENSTATE_READY)
  99. {
  100. llwarns << "No Internet stream to start playing !" << llendl;
  101. return NULL;
  102. }
  103. if (mStreamChannel)
  104. {
  105. // We already have a channel for this stream.
  106. LL_DEBUGS("FMOD") << "We already have a stream for channel: "
  107. << std::hex << mStreamChannel << std::dec << LL_ENDL;
  108. return mStreamChannel;
  109. }
  110. LL_DEBUGS("FMOD") << "Starting stream..." << LL_ENDL;
  111. if (!checkFMerr(mSystem->playSound(mInternetStream, mChannelGroup, true,
  112. &mStreamChannel),
  113. "FMOD::System::playSound"))
  114. {
  115. LL_DEBUGS("FMOD") << "Stream started." << LL_ENDL;
  116. }
  117. return mStreamChannel;
  118. }
  119. bool LLAudioStreamManagerFMOD::releaseStream(bool force)
  120. {
  121. if (mInternetStream)
  122. {
  123. bool timed_out = false;
  124. if (mFirstReleaseAttempt == 0.f)
  125. {
  126. mFirstReleaseAttempt = (F32)LLTimer::getElapsedSeconds();
  127. }
  128. else if ((F32)LLTimer::getElapsedSeconds() -
  129. mFirstReleaseAttempt >= FORCE_RELEASE_DELAY)
  130. {
  131. LL_DEBUGS("FMOD") << "Stopped stream " << mInternetStreamURL
  132. << " timed out." << LL_ENDL;
  133. timed_out = true;
  134. }
  135. FMOD_OPENSTATE state = getOpenState();
  136. if (!timed_out && !force && state != FMOD_OPENSTATE_READY &&
  137. state != FMOD_OPENSTATE_ERROR)
  138. {
  139. LL_DEBUGS("FMOD") << "Stream " << mInternetStreamURL
  140. << " not yet ready for release. State is: "
  141. << (S32)state << " - Delaying." << LL_ENDL;
  142. return false;
  143. }
  144. LL_DEBUGS("FMOD") << "Attempting to release stream "
  145. << mInternetStreamURL << " (current state is: "
  146. << (S32)state << ")..." << LL_ENDL;
  147. if (force || timed_out)
  148. {
  149. llwarns << "Failed to release stream: " << mInternetStreamURL
  150. << " - Force-closing it." << llendl;
  151. }
  152. else if (mInternetStream->release() == FMOD_OK)
  153. {
  154. LL_DEBUGS("FMOD") << "Stream " << mInternetStreamURL
  155. << " released." << LL_ENDL;
  156. }
  157. else
  158. {
  159. LL_DEBUGS("FMOD") << "Failed to release stream: "
  160. << mInternetStreamURL << " - Delaying."
  161. << LL_ENDL;
  162. return false;
  163. }
  164. mStreamChannel = NULL;
  165. mInternetStream = NULL;
  166. }
  167. return true;
  168. }
  169. FMOD_OPENSTATE LLAudioStreamManagerFMOD::getOpenState(unsigned int* pctbuffered,
  170. bool* starving,
  171. bool* diskbusy)
  172. {
  173. FMOD_OPENSTATE state = FMOD_OPENSTATE_ERROR;
  174. if (mInternetStream)
  175. {
  176. mInternetStream->getOpenState(&state, pctbuffered, starving, diskbusy);
  177. }
  178. return state;
  179. }
  180. //---------------------------------------------------------------------------
  181. // Internet Streaming
  182. //---------------------------------------------------------------------------
  183. LLStreamingAudio_FMOD::LLStreamingAudio_FMOD(FMOD::System* system)
  184. : mSystem(system),
  185. mBufferMilliSeconds(10000U),
  186. mCurrentInternetStreamp(NULL),
  187. mFMODInternetStreamChannelp(NULL),
  188. mGain(1.0f),
  189. mArtist(),
  190. mTitle(),
  191. mLastStarved(0.f),
  192. mPendingStart(false),
  193. mNewMetaData(false)
  194. {
  195. // Audio to buffer size for the audio card. mBufferMilliSeconds must be
  196. // larger than the usual Second Life frame stutter time.
  197. U32 size = ESTIMATED_BIT_RATE * BYTES_PER_KBIT * mBufferMilliSeconds /
  198. 1000U; // in seconds
  199. checkFMerr(mSystem->setStreamBufferSize(size, FMOD_TIMEUNIT_RAWBYTES),
  200. "FMOD::System::setStreamBufferSize");
  201. checkFMerr(mSystem->createChannelGroup("stream", &mStreamGroup),
  202. "FMOD::System::createChannelGroup");
  203. }
  204. LLStreamingAudio_FMOD::~LLStreamingAudio_FMOD()
  205. {
  206. // Stop streaming audio if needed
  207. stop();
  208. mURL.clear();
  209. F32 start = (F32)LLTimer::getElapsedSeconds();
  210. while (!releaseDeadStreams((F32)LLTimer::getElapsedSeconds() - start >=
  211. FORCE_RELEASE_DELAY)) ;
  212. if (mStreamGroup)
  213. {
  214. checkFMerr(mStreamGroup->release(), "FMOD::ChannelGroup::release");
  215. mStreamGroup = NULL;
  216. }
  217. }
  218. void LLStreamingAudio_FMOD::setBufferSizes(U32 streambuffertime,
  219. U32 decodebuffertime)
  220. {
  221. if (mSystem) // Paranoia
  222. {
  223. mBufferMilliSeconds = llmin(streambuffertime, 3000U); // in ms
  224. decodebuffertime = llmin(decodebuffertime, 500U); // in ms
  225. U32 size = ESTIMATED_BIT_RATE * BYTES_PER_KBIT * mBufferMilliSeconds /
  226. 1000U; // in seconds
  227. checkFMerr(mSystem->setStreamBufferSize(size, FMOD_TIMEUNIT_RAWBYTES),
  228. "FMOD::System::setStreamBufferSize");
  229. FMOD_ADVANCEDSETTINGS settings;
  230. memset(&settings, 0, sizeof(settings));
  231. settings.cbSize = sizeof(settings);
  232. settings.defaultDecodeBufferSize = decodebuffertime;
  233. checkFMerr(mSystem->setAdvancedSettings(&settings),
  234. "FMOD::System::setAdvancedSettings");
  235. }
  236. }
  237. void LLStreamingAudio_FMOD::start(const std::string& url)
  238. {
  239. // "stop" stream if needed, but don't clear url, etc in case
  240. // url == mInternetStreamURL
  241. stop();
  242. if (url.empty())
  243. {
  244. llinfos << "Set Internet stream to none." << llendl;
  245. mURL.clear();
  246. mPendingStart = false;
  247. }
  248. else
  249. {
  250. mURL = url;
  251. mPendingStart = true;
  252. }
  253. }
  254. bool LLStreamingAudio_FMOD::releaseDeadStreams(bool force)
  255. {
  256. for (std::list<LLAudioStreamManagerFMOD*>::iterator
  257. iter = mDeadStreams.begin();
  258. iter != mDeadStreams.end(); )
  259. {
  260. LLAudioStreamManagerFMOD* streamp = *iter;
  261. if (!streamp) // Paranoia
  262. {
  263. llwarns << "Found a NULL stream in dead streams list ! Removing."
  264. << llendl;
  265. iter = mDeadStreams.erase(iter);
  266. }
  267. else if (streamp->releaseStream(force))
  268. {
  269. llinfos << "Closed dead stream: " << streamp->getURL() << llendl;
  270. delete streamp;
  271. iter = mDeadStreams.erase(iter);
  272. }
  273. else
  274. {
  275. ++iter;
  276. }
  277. }
  278. return mDeadStreams.empty();
  279. }
  280. void LLStreamingAudio_FMOD::update()
  281. {
  282. // Kill dead Internet streams
  283. if (!releaseDeadStreams(false))
  284. {
  285. return;
  286. }
  287. if (mPendingStart && mSystem && mStreamGroup)
  288. {
  289. llinfos << "Starting Internet stream: " << mURL << llendl;
  290. mCurrentInternetStreamp = new LLAudioStreamManagerFMOD(mSystem,
  291. mStreamGroup,
  292. mURL);
  293. mPendingStart = false;
  294. }
  295. // Do not do anything if there is no stream playing
  296. if (!mCurrentInternetStreamp)
  297. {
  298. return;
  299. }
  300. unsigned int progress;
  301. bool starving;
  302. bool diskbusy;
  303. FMOD_OPENSTATE open_state =
  304. mCurrentInternetStreamp->getOpenState(&progress, &starving, &diskbusy);
  305. if (open_state == FMOD_OPENSTATE_READY)
  306. {
  307. // Stream is live. Start it if it is ready.
  308. if (!mFMODInternetStreamChannelp &&
  309. (mFMODInternetStreamChannelp = mCurrentInternetStreamp->startStream()))
  310. {
  311. LL_DEBUGS("FMOD") << "Stream " << mCurrentInternetStreamp->getURL()
  312. << " is live, starting it." << LL_ENDL;
  313. // Reset volume to previously set volume
  314. setGain(getGain());
  315. mFMODInternetStreamChannelp->setPaused(false);
  316. mFMODInternetStreamChannelp->setMute(false);
  317. mLastStarved = 0.f;
  318. LL_DEBUGS("FMOD") << "Stream started." << LL_ENDL;
  319. }
  320. }
  321. else if (open_state == FMOD_OPENSTATE_ERROR)
  322. {
  323. LL_DEBUGS("FMOD") << "Stream '" << mCurrentInternetStreamp->getURL()
  324. << "' reports an error, stopping it." << LL_ENDL;
  325. stop();
  326. LL_DEBUGS("FMOD") << "Stream stopped." << LL_ENDL;
  327. return;
  328. }
  329. if (mFMODInternetStreamChannelp)
  330. {
  331. FMOD::Sound* sound = NULL;
  332. if (mFMODInternetStreamChannelp->getCurrentSound(&sound) == FMOD_OK &&
  333. sound)
  334. {
  335. FMOD_TAG tag;
  336. S32 tagcount, dirtytagcount;
  337. if (sound->getNumTags(&tagcount, &dirtytagcount) == FMOD_OK &&
  338. dirtytagcount)
  339. {
  340. S32 count = llclamp(tagcount, 0, 1024); // Paranoia
  341. if (count != tagcount)
  342. {
  343. llwarns << "Bogus tag count: " << tagcount
  344. << " - Clamped to: " << count << llendl;
  345. }
  346. for (S32 i = 0; i < count; ++i)
  347. {
  348. if (sound->getTag(NULL, i, &tag) != FMOD_OK)
  349. {
  350. continue;
  351. }
  352. std::string token = tag.name;
  353. LLStringUtil::toLower(token);
  354. LL_DEBUGS("FMOD") << "Stream tag name: " << token
  355. << " - type: " << tag.type
  356. << " - data type: " << tag.datatype
  357. << LL_ENDL;
  358. if (tag.type == FMOD_TAGTYPE_FMOD)
  359. {
  360. if (token == "sample rate change")
  361. {
  362. llinfos << "Stream forced changing sample rate to "
  363. << *((float*)tag.data) << llendl;
  364. mFMODInternetStreamChannelp->setFrequency(*((float*)tag.data));
  365. }
  366. }
  367. else if (tag.type == FMOD_TAGTYPE_ASF ||
  368. tag.datatype == FMOD_TAGDATATYPE_STRING)
  369. {
  370. if (token == "title" || token == "tit2")
  371. {
  372. std::string tmp;
  373. tmp.assign((char*)tag.data);
  374. if (mTitle != tmp)
  375. {
  376. mTitle = tmp;
  377. mNewMetaData = true;
  378. }
  379. }
  380. else if (token == "artist" || token == "tpe1" ||
  381. token == "wm/albumtitle")
  382. {
  383. std::string tmp;
  384. tmp.assign((char*)tag.data);
  385. if (mArtist != tmp)
  386. {
  387. mArtist = tmp;
  388. mNewMetaData = true;
  389. }
  390. }
  391. }
  392. }
  393. }
  394. if (starving)
  395. {
  396. bool paused = false;
  397. mFMODInternetStreamChannelp->getPaused(&paused);
  398. if (!paused && mLastStarved != 0.f)
  399. {
  400. llinfos << "Stream starvation detected, muting stream audio until it clears."
  401. << llendl;
  402. LL_DEBUGS("FMOD") << "diskbusy = " << diskbusy
  403. << " - progress = " << progress
  404. << LL_ENDL;
  405. mFMODInternetStreamChannelp->setMute(true);
  406. }
  407. mLastStarved = (F32)LLTimer::getElapsedSeconds();
  408. }
  409. else if (mLastStarved != 0.f && progress > 50)
  410. {
  411. // more than 50% of the buffer is full, resume music playing
  412. F32 buffer_fill_time = ((F32)LLTimer::getElapsedSeconds() -
  413. mLastStarved) * 100.f / (F32)progress;
  414. F32 buffer_size_seconds = (F32)mBufferMilliSeconds / 1000.f;
  415. if (buffer_fill_time > buffer_size_seconds)
  416. {
  417. llwarns << "Starvation state cleared, resuming streaming music playing but new starvations will likely occur (time required to fill the buffer = "
  418. << buffer_fill_time
  419. << " - buffer size in seconds = "
  420. << buffer_size_seconds << ")." << llendl;
  421. }
  422. else
  423. {
  424. llinfos << "Starvation state cleared, resuming streaming music playing."
  425. << llendl;
  426. }
  427. mLastStarved = 0.f;
  428. mFMODInternetStreamChannelp->setMute(false);
  429. }
  430. }
  431. }
  432. }
  433. void LLStreamingAudio_FMOD::stop()
  434. {
  435. mLastStarved = 0.f;
  436. mNewMetaData = false;
  437. mArtist.clear();
  438. mTitle.clear();
  439. if (mFMODInternetStreamChannelp)
  440. {
  441. LL_DEBUGS("FMOD") << "Stopping stream..." << LL_ENDL;
  442. checkFMerr(mFMODInternetStreamChannelp->setPaused(true),
  443. "FMOD::Channel::setPaused");
  444. checkFMerr(mFMODInternetStreamChannelp->setPriority(0),
  445. "FMOD::Channel::setPriority");
  446. mFMODInternetStreamChannelp = NULL;
  447. }
  448. if (mCurrentInternetStreamp)
  449. {
  450. if (mCurrentInternetStreamp->releaseStream())
  451. {
  452. llinfos << "Released Internet stream: "
  453. << mCurrentInternetStreamp->getURL() << llendl;
  454. delete mCurrentInternetStreamp;
  455. }
  456. else
  457. {
  458. llinfos << "Pushing Internet stream to dead list: "
  459. << mCurrentInternetStreamp->getURL() << llendl;
  460. mDeadStreams.push_back(mCurrentInternetStreamp);
  461. }
  462. mCurrentInternetStreamp = NULL;
  463. }
  464. }
  465. void LLStreamingAudio_FMOD::pause(S32 pauseopt)
  466. {
  467. LL_DEBUGS("FMOD") << "called with pauseopt = " << pauseopt << LL_ENDL;
  468. if (pauseopt < 0)
  469. {
  470. pauseopt = mCurrentInternetStreamp ? 1 : 0;
  471. LL_DEBUGS("FMOD") << "pauseopt < 0 -> " << pauseopt << LL_ENDL;
  472. }
  473. if (pauseopt)
  474. {
  475. if (mCurrentInternetStreamp)
  476. {
  477. LL_DEBUGS("FMOD") << "Stopping stream" << LL_ENDL;
  478. stop();
  479. }
  480. }
  481. else
  482. {
  483. LL_DEBUGS("FMOD") << "Starting stream" << LL_ENDL;
  484. start(getURL());
  485. }
  486. }
  487. // A stream is "playing" if it has been requested to start. That does not
  488. // necessarily mean audio is coming out of the speakers.
  489. S32 LLStreamingAudio_FMOD::isPlaying()
  490. {
  491. if (mCurrentInternetStreamp)
  492. {
  493. return 1; // Active and playing
  494. }
  495. else if (!mURL.empty())
  496. {
  497. return 2; // "Paused"
  498. }
  499. else
  500. {
  501. return 0;
  502. }
  503. }
  504. void LLStreamingAudio_FMOD::setGain(F32 vol)
  505. {
  506. mGain = vol;
  507. if (mFMODInternetStreamChannelp)
  508. {
  509. vol = llclamp(vol, 0.f, 1.f);
  510. mFMODInternetStreamChannelp->setVolume(vol);
  511. }
  512. }