AudioStream.cpp (25359B)
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 "AudioStream.h" 7 8 #include <math.h> 9 #include <stdio.h> 10 #include <string.h> 11 12 #include <algorithm> 13 14 #include "AudioConverter.h" 15 #include "CubebUtils.h" 16 #include "UnderrunHandler.h" 17 #include "VideoUtils.h" 18 #include "mozilla/Logging.h" 19 #include "mozilla/Monitor.h" 20 #include "mozilla/Mutex.h" 21 #include "mozilla/Sprintf.h" 22 #include "mozilla/dom/AudioDeviceInfo.h" 23 #include "nsNativeCharsetUtils.h" 24 #include "nsPrintfCString.h" 25 #include "prdtoa.h" 26 #if defined(XP_WIN) 27 # include "nsXULAppAPI.h" 28 #endif 29 #include "CallbackThreadRegistry.h" 30 #include "RLBoxSoundTouch.h" 31 #include "Tracing.h" 32 #include "mozilla/StaticPrefs_media.h" 33 #include "webaudio/blink/DenormalDisabler.h" 34 35 namespace mozilla { 36 37 LazyLogModule gAudioStreamLog("AudioStream"); 38 // For simple logs 39 #define LOG(x, ...) \ 40 MOZ_LOG(gAudioStreamLog, mozilla::LogLevel::Debug, \ 41 ("%p " x, this, ##__VA_ARGS__)) 42 #define LOGW(x, ...) \ 43 MOZ_LOG(gAudioStreamLog, mozilla::LogLevel::Warning, \ 44 ("%p " x, this, ##__VA_ARGS__)) 45 #define LOGE(x, ...) \ 46 NS_DebugBreak(NS_DEBUG_WARNING, \ 47 nsPrintfCString("%p " x, this, ##__VA_ARGS__).get(), nullptr, \ 48 __FILE__, __LINE__) 49 50 /** 51 * Keep a list of frames sent to the audio engine in each DataCallback along 52 * with the playback rate at the moment. Since the playback rate and number of 53 * underrun frames can vary in each callback. We need to keep the whole history 54 * in order to calculate the playback position of the audio engine correctly. 55 */ 56 class FrameHistory { 57 struct Chunk { 58 uint32_t servicedFrames; 59 uint32_t totalFrames; 60 uint32_t rate; 61 }; 62 63 template <typename T> 64 static T FramesToUs(uint32_t frames, uint32_t rate) { 65 return static_cast<T>(frames) * USECS_PER_S / rate; 66 } 67 68 public: 69 FrameHistory() : mBaseOffset(0), mBasePosition(0) {} 70 71 void Append(uint32_t aServiced, uint32_t aUnderrun, uint32_t aRate) { 72 /* In most case where playback rate stays the same and we don't underrun 73 * frames, we are able to merge chunks to avoid lose of precision to add up 74 * in compressing chunks into |mBaseOffset| and |mBasePosition|. 75 */ 76 if (!mChunks.IsEmpty()) { 77 Chunk& c = mChunks.LastElement(); 78 // 2 chunks (c1 and c2) can be merged when rate is the same and 79 // adjacent frames are zero. That is, underrun frames in c1 are zero 80 // or serviced frames in c2 are zero. 81 if (c.rate == aRate && 82 (c.servicedFrames == c.totalFrames || aServiced == 0)) { 83 c.servicedFrames += aServiced; 84 c.totalFrames += aServiced + aUnderrun; 85 return; 86 } 87 } 88 Chunk* p = mChunks.AppendElement(); 89 p->servicedFrames = aServiced; 90 p->totalFrames = aServiced + aUnderrun; 91 p->rate = aRate; 92 } 93 94 /** 95 * @param frames The playback position in frames of the audio engine. 96 * @return The playback position in microseconds of the audio engine, 97 * adjusted by playback rate changes and underrun frames. 98 */ 99 int64_t GetPosition(int64_t frames) { 100 // playback position should not go backward. 101 MOZ_ASSERT(frames >= mBaseOffset); 102 while (true) { 103 if (mChunks.IsEmpty()) { 104 return static_cast<int64_t>(mBasePosition); 105 } 106 const Chunk& c = mChunks[0]; 107 if (frames <= mBaseOffset + c.totalFrames) { 108 uint32_t delta = frames - mBaseOffset; 109 delta = std::min(delta, c.servicedFrames); 110 return static_cast<int64_t>(mBasePosition) + 111 FramesToUs<int64_t>(delta, c.rate); 112 } 113 // Since the playback position of the audio engine will not go backward, 114 // we are able to compress chunks so that |mChunks| won't grow 115 // unlimitedly. Note that we lose precision in converting integers into 116 // floats and inaccuracy will accumulate over time. However, for a 24hr 117 // long, sample rate = 44.1k file, the error will be less than 1 118 // microsecond after playing 24 hours. So we are fine with that. 119 mBaseOffset += c.totalFrames; 120 mBasePosition += FramesToUs<double>(c.servicedFrames, c.rate); 121 mChunks.RemoveElementAt(0); 122 } 123 } 124 125 private: 126 AutoTArray<Chunk, 7> mChunks; 127 int64_t mBaseOffset; 128 double mBasePosition; 129 }; 130 131 AudioStream::AudioStream(DataSource& aSource, uint32_t aInRate, 132 uint32_t aOutputChannels, 133 AudioConfig::ChannelLayout::ChannelMap aChannelMap) 134 : mTimeStretcher(nullptr), 135 mAudioClock(aInRate), 136 mChannelMap(aChannelMap), 137 mMonitor("AudioStream"), 138 mOutChannels(aOutputChannels), 139 mState(INITIALIZED), 140 mDataSource(aSource), 141 mAudioThreadId(ProfilerThreadId{}), 142 mSandboxed(CubebUtils::SandboxEnabled()), 143 mPlaybackComplete(false), 144 mPlaybackRate(1.0f), 145 mPreservesPitch(true), 146 mCallbacksStarted(false) {} 147 148 AudioStream::~AudioStream() { 149 LOG("deleted, state %d", mState.load()); 150 MOZ_ASSERT(mState == SHUTDOWN && !mCubebStream, 151 "Should've called ShutDown() before deleting an AudioStream"); 152 } 153 154 size_t AudioStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { 155 size_t amount = aMallocSizeOf(this); 156 157 // Possibly add in the future: 158 // - mTimeStretcher 159 // - mCubebStream 160 161 return amount; 162 } 163 164 nsresult AudioStream::EnsureTimeStretcherInitialized() { 165 AssertIsOnAudioThread(); 166 if (!mTimeStretcher) { 167 auto timestretcher = MakeUnique<RLBoxSoundTouch>(); 168 if (!timestretcher || !timestretcher->Init()) { 169 return NS_ERROR_FAILURE; 170 } 171 mTimeStretcher = timestretcher.release(); 172 173 mTimeStretcher->setSampleRate(mAudioClock.GetInputRate()); 174 mTimeStretcher->setChannels(mOutChannels); 175 mTimeStretcher->setPitch(1.0); 176 177 // SoundTouch v2.1.2 uses automatic time-stretch settings with the following 178 // values: 179 // Tempo 0.5: 90ms sequence, 20ms seekwindow, 8ms overlap 180 // Tempo 2.0: 40ms sequence, 15ms seekwindow, 8ms overlap 181 // We are going to use a smaller 10ms sequence size to improve speech 182 // clarity, giving more resolution at high tempo and less reverb at low 183 // tempo. Maintain 15ms seekwindow and 8ms overlap for smoothness. 184 mTimeStretcher->setSetting( 185 SETTING_SEQUENCE_MS, 186 StaticPrefs::media_audio_playbackrate_soundtouch_sequence_ms()); 187 mTimeStretcher->setSetting( 188 SETTING_SEEKWINDOW_MS, 189 StaticPrefs::media_audio_playbackrate_soundtouch_seekwindow_ms()); 190 mTimeStretcher->setSetting( 191 SETTING_OVERLAP_MS, 192 StaticPrefs::media_audio_playbackrate_soundtouch_overlap_ms()); 193 } 194 return NS_OK; 195 } 196 197 nsresult AudioStream::SetPlaybackRate(double aPlaybackRate) { 198 TRACE_COMMENT("AudioStream::SetPlaybackRate", "%f", aPlaybackRate); 199 NS_ASSERTION( 200 aPlaybackRate > 0.0, 201 "Can't handle negative or null playbackrate in the AudioStream."); 202 if (aPlaybackRate == mPlaybackRate) { 203 return NS_OK; 204 } 205 206 mPlaybackRate = static_cast<float>(aPlaybackRate); 207 208 return NS_OK; 209 } 210 211 nsresult AudioStream::SetPreservesPitch(bool aPreservesPitch) { 212 TRACE_COMMENT("AudioStream::SetPreservesPitch", "%d", aPreservesPitch); 213 if (aPreservesPitch == mPreservesPitch) { 214 return NS_OK; 215 } 216 217 mPreservesPitch = aPreservesPitch; 218 219 return NS_OK; 220 } 221 222 template <typename Function, typename... Args> 223 int AudioStream::InvokeCubeb(Function aFunction, Args&&... aArgs) { 224 mMonitor.AssertCurrentThreadOwns(); 225 MonitorAutoUnlock mon(mMonitor); 226 return aFunction(mCubebStream.get(), std::forward<Args>(aArgs)...); 227 } 228 229 nsresult AudioStream::Init(AudioDeviceInfo* aSinkInfo) 230 MOZ_NO_THREAD_SAFETY_ANALYSIS { 231 auto startTime = TimeStamp::Now(); 232 TRACE("AudioStream::Init"); 233 234 LOG("%s channels: %d, rate: %d", __FUNCTION__, mOutChannels, 235 mAudioClock.GetInputRate()); 236 237 mSinkInfo = aSinkInfo; 238 239 cubeb_stream_params params; 240 params.rate = mAudioClock.GetInputRate(); 241 params.channels = mOutChannels; 242 params.layout = static_cast<uint32_t>(mChannelMap); 243 params.format = CubebUtils::ToCubebFormat<AUDIO_OUTPUT_FORMAT>::value; 244 params.prefs = CubebUtils::GetDefaultStreamPrefs(CUBEB_DEVICE_TYPE_OUTPUT); 245 params.input_params = CUBEB_INPUT_PROCESSING_PARAM_NONE; 246 247 // This is noop if MOZ_DUMP_AUDIO is not set. 248 mDumpFile.Open("AudioStream", mOutChannels, mAudioClock.GetInputRate()); 249 250 RefPtr<CubebUtils::CubebHandle> handle = CubebUtils::GetCubeb(); 251 if (!handle) { 252 LOGE("Can't get cubeb context!"); 253 CubebUtils::ReportCubebStreamInitFailure(true); 254 return NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR; 255 } 256 257 mCubeb = handle; 258 return OpenCubeb(handle->Context(), params, startTime, 259 CubebUtils::GetFirstStream()); 260 } 261 262 nsresult AudioStream::OpenCubeb(cubeb* aContext, cubeb_stream_params& aParams, 263 TimeStamp aStartTime, bool aIsFirst) { 264 TRACE("AudioStream::OpenCubeb"); 265 MOZ_ASSERT(aContext); 266 267 cubeb_stream* stream = nullptr; 268 /* Convert from milliseconds to frames. */ 269 uint32_t latency_frames = 270 CubebUtils::GetCubebPlaybackLatencyInMilliseconds() * aParams.rate / 1000; 271 cubeb_devid deviceID = nullptr; 272 if (mSinkInfo && mSinkInfo->DeviceID()) { 273 deviceID = mSinkInfo->DeviceID(); 274 } 275 if (CubebUtils::CubebStreamInit(aContext, &stream, "AudioStream", nullptr, 276 nullptr, deviceID, &aParams, latency_frames, 277 DataCallback_S, StateCallback_S, 278 this) == CUBEB_OK) { 279 mCubebStream.reset(stream); 280 CubebUtils::ReportCubebBackendUsed(); 281 } else { 282 LOGE("OpenCubeb() failed to init cubeb"); 283 CubebUtils::ReportCubebStreamInitFailure(aIsFirst); 284 return NS_ERROR_FAILURE; 285 } 286 287 TimeDuration timeDelta = TimeStamp::Now() - aStartTime; 288 LOG("creation time %sfirst: %u ms", aIsFirst ? "" : "not ", 289 (uint32_t)timeDelta.ToMilliseconds()); 290 291 return NS_OK; 292 } 293 294 void AudioStream::SetVolume(double aVolume) { 295 TRACE_COMMENT("AudioStream::SetVolume", "%f", aVolume); 296 MOZ_ASSERT(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume"); 297 298 MOZ_ASSERT(mState != SHUTDOWN, "Don't set volume after shutdown."); 299 if (mState == ERRORED) { 300 return; 301 } 302 303 MonitorAutoLock mon(mMonitor); 304 if (InvokeCubeb(cubeb_stream_set_volume, 305 aVolume * CubebUtils::GetVolumeScale()) != CUBEB_OK) { 306 LOGE("Could not change volume on cubeb stream."); 307 } 308 } 309 310 void AudioStream::SetStreamName(const nsAString& aStreamName) { 311 TRACE("AudioStream::SetStreamName"); 312 313 nsAutoCString aRawStreamName; 314 nsresult rv = NS_CopyUnicodeToNative(aStreamName, aRawStreamName); 315 316 if (NS_FAILED(rv) || aStreamName.IsEmpty()) { 317 return; 318 } 319 320 MonitorAutoLock mon(mMonitor); 321 int r = InvokeCubeb(cubeb_stream_set_name, aRawStreamName.get()); 322 if (r && r != CUBEB_ERROR_NOT_SUPPORTED) { 323 LOGE("Could not set cubeb stream name."); 324 } 325 } 326 327 RefPtr<MediaSink::EndedPromise> AudioStream::Start() { 328 TRACE("AudioStream::Start"); 329 MOZ_ASSERT(mState == INITIALIZED); 330 mState = STARTED; 331 RefPtr<MediaSink::EndedPromise> promise; 332 { 333 MonitorAutoLock mon(mMonitor); 334 // As cubeb might call audio stream's state callback very soon after we 335 // start cubeb, we have to create the promise beforehand in order to handle 336 // the case where we immediately get `drained`. 337 promise = mEndedPromise.Ensure(__func__); 338 mPlaybackComplete = false; 339 340 if (InvokeCubeb(cubeb_stream_start) != CUBEB_OK) { 341 mState = ERRORED; 342 mEndedPromise.RejectIfExists(NS_ERROR_FAILURE, __func__); 343 } 344 345 LOG("started, state %s", mState == STARTED ? "STARTED" 346 : mState == DRAINED ? "DRAINED" 347 : "ERRORED"); 348 } 349 return promise; 350 } 351 352 void AudioStream::Pause() { 353 TRACE("AudioStream::Pause"); 354 MOZ_ASSERT(mState != INITIALIZED, "Must be Start()ed."); 355 MOZ_ASSERT(mState != STOPPED, "Already Pause()ed."); 356 MOZ_ASSERT(mState != SHUTDOWN, "Already ShutDown()ed."); 357 358 // Do nothing if we are already drained or errored. 359 if (mState == DRAINED || mState == ERRORED) { 360 return; 361 } 362 363 MonitorAutoLock mon(mMonitor); 364 if (InvokeCubeb(cubeb_stream_stop) != CUBEB_OK) { 365 mState = ERRORED; 366 } else if (mState != DRAINED && mState != ERRORED) { 367 // Don't transition to other states if we are already 368 // drained or errored. 369 mState = STOPPED; 370 } 371 } 372 373 void AudioStream::Resume() { 374 TRACE("AudioStream::Resume"); 375 MOZ_ASSERT(mState != INITIALIZED, "Must be Start()ed."); 376 MOZ_ASSERT(mState != STARTED, "Already Start()ed."); 377 MOZ_ASSERT(mState != SHUTDOWN, "Already ShutDown()ed."); 378 379 // Do nothing if we are already drained or errored. 380 if (mState == DRAINED || mState == ERRORED) { 381 return; 382 } 383 384 MonitorAutoLock mon(mMonitor); 385 if (InvokeCubeb(cubeb_stream_start) != CUBEB_OK) { 386 mState = ERRORED; 387 } else if (mState != DRAINED && mState != ERRORED) { 388 // Don't transition to other states if we are already 389 // drained or errored. 390 mState = STARTED; 391 } 392 } 393 394 void AudioStream::ShutDown() { 395 TRACE("AudioStream::ShutDown"); 396 LOG("ShutDown, state %d", mState.load()); 397 398 MonitorAutoLock mon(mMonitor); 399 if (mCubebStream) { 400 // Force stop to put the cubeb stream in a stable state before deletion. 401 InvokeCubeb(cubeb_stream_stop); 402 // Must not try to shut down cubeb from within the lock! wasapi may still 403 // call our callback after Pause()/stop()!?! Bug 996162 404 cubeb_stream* cubeb = mCubebStream.release(); 405 MonitorAutoUnlock unlock(mMonitor); 406 cubeb_stream_destroy(cubeb); 407 } 408 409 // After `cubeb_stream_stop` has been called, there is no audio thread 410 // anymore. We can delete the time stretcher. 411 if (mTimeStretcher) { 412 delete mTimeStretcher; 413 mTimeStretcher = nullptr; 414 } 415 416 mState = SHUTDOWN; 417 mEndedPromise.ResolveIfExists(true, __func__); 418 } 419 420 int64_t AudioStream::GetPosition() { 421 TRACE("AudioStream::GetPosition"); 422 #ifndef XP_MACOSX 423 MonitorAutoLock mon(mMonitor); 424 #endif 425 int64_t frames = GetPositionInFramesUnlocked(); 426 return frames >= 0 ? mAudioClock.GetPosition(frames) : -1; 427 } 428 429 int64_t AudioStream::GetPositionInFrames() { 430 TRACE("AudioStream::GetPositionInFrames"); 431 #ifndef XP_MACOSX 432 MonitorAutoLock mon(mMonitor); 433 #endif 434 int64_t frames = GetPositionInFramesUnlocked(); 435 436 return frames >= 0 ? mAudioClock.GetPositionInFrames(frames) : -1; 437 } 438 439 int64_t AudioStream::GetPositionInFramesUnlocked() { 440 TRACE("AudioStream::GetPositionInFramesUnlocked"); 441 #ifndef XP_MACOSX 442 mMonitor.AssertCurrentThreadOwns(); 443 #endif 444 445 if (mState == ERRORED) { 446 return -1; 447 } 448 449 uint64_t position = 0; 450 int rv; 451 452 #ifndef XP_MACOSX 453 rv = InvokeCubeb(cubeb_stream_get_position, &position); 454 #else 455 rv = cubeb_stream_get_position(mCubebStream.get(), &position); 456 #endif 457 458 if (rv != CUBEB_OK) { 459 return -1; 460 } 461 return static_cast<int64_t>(std::min<uint64_t>(position, INT64_MAX)); 462 } 463 464 bool AudioStream::IsValidAudioFormat(Chunk* aChunk) { 465 if (aChunk->Rate() != mAudioClock.GetInputRate()) { 466 LOGW("mismatched sample %u, mInRate=%u", aChunk->Rate(), 467 mAudioClock.GetInputRate()); 468 return false; 469 } 470 471 return aChunk->Channels() <= 8; 472 } 473 474 void AudioStream::GetUnprocessed(AudioBufferWriter& aWriter) { 475 TRACE("AudioStream::GetUnprocessed"); 476 AssertIsOnAudioThread(); 477 // Flush the timestretcher pipeline, if we were playing using a playback rate 478 // other than 1.0. 479 if (mTimeStretcher) { 480 // Get number of samples and based on this either receive samples or write 481 // silence. At worst, the attacker can supply weird sound samples or 482 // result in us writing silence. 483 auto numSamples = mTimeStretcher->numSamples().unverified_safe_because( 484 "We only use this to decide whether to receive samples or write " 485 "silence."); 486 if (numSamples) { 487 RLBoxSoundTouch* timeStretcher = mTimeStretcher; 488 aWriter.Write( 489 [timeStretcher](AudioDataValue* aPtr, uint32_t aFrames) { 490 return timeStretcher->receiveSamples(aPtr, aFrames); 491 }, 492 aWriter.Available()); 493 494 // TODO: There might be still unprocessed samples in the stretcher. 495 // We should either remove or flush them so they won't be in the output 496 // next time we switch a playback rate other than 1.0. 497 mTimeStretcher->numUnprocessedSamples().copy_and_verify([](auto samples) { 498 NS_WARNING_ASSERTION(samples == 0, "no samples"); 499 }); 500 } else { 501 // Don't need it anymore: playbackRate is 1.0, and the time stretcher has 502 // been flushed. 503 delete mTimeStretcher; 504 mTimeStretcher = nullptr; 505 } 506 } 507 508 while (aWriter.Available() > 0) { 509 uint32_t count = mDataSource.PopFrames(aWriter.Ptr(), aWriter.Available(), 510 mAudioThreadChanged); 511 if (count == 0) { 512 break; 513 } 514 aWriter.Advance(count); 515 } 516 } 517 518 void AudioStream::GetTimeStretched(AudioBufferWriter& aWriter) { 519 TRACE("AudioStream::GetTimeStretched"); 520 AssertIsOnAudioThread(); 521 if (EnsureTimeStretcherInitialized() != NS_OK) { 522 return; 523 } 524 525 uint32_t toPopFrames = 526 ceil(aWriter.Available() * mAudioClock.GetPlaybackRate()); 527 528 if (!mTimeStretcher) { 529 return; 530 } 531 532 // At each iteration, get number of samples and (based on this) write from 533 // the data source or silence. At worst, if the number of samples is a lie 534 // (i.e., under attacker control) we'll either not write anything or keep 535 // writing noise. This is safe because all the memory operations within the 536 // loop (and after) are checked. 537 while (mTimeStretcher->numSamples().unverified_safe_because( 538 "Only used to decide whether to put samples.") < 539 aWriter.Available()) { 540 // pop into a temp buffer, and put into the stretcher. 541 AutoTArray<AudioDataValue, 1000> buf; 542 auto size = CheckedUint32(mOutChannels) * toPopFrames; 543 if (!size.isValid()) { 544 // The overflow should not happen in normal case. 545 LOGW("Invalid member data: %d channels, %d frames", mOutChannels, 546 toPopFrames); 547 return; 548 } 549 buf.SetLength(size.value()); 550 // ensure no variable channel count or something like that 551 uint32_t count = 552 mDataSource.PopFrames(buf.Elements(), toPopFrames, mAudioThreadChanged); 553 if (count == 0) { 554 break; 555 } 556 mTimeStretcher->putSamples(buf.Elements(), count); 557 } 558 559 auto* timeStretcher = mTimeStretcher; 560 aWriter.Write( 561 [timeStretcher](AudioDataValue* aPtr, uint32_t aFrames) { 562 return timeStretcher->receiveSamples(aPtr, aFrames); 563 }, 564 aWriter.Available()); 565 } 566 567 bool AudioStream::CheckThreadIdChanged() { 568 ProfilerThreadId id = profiler_current_thread_id(); 569 if (id != mAudioThreadId) { 570 mAudioThreadId = id; 571 mAudioThreadChanged = true; 572 return true; 573 } 574 mAudioThreadChanged = false; 575 return false; 576 } 577 578 void AudioStream::AssertIsOnAudioThread() const { 579 // This can be called right after CheckThreadIdChanged, because the audio 580 // thread can change when not sandboxed. 581 MOZ_ASSERT(mAudioThreadId.load() == profiler_current_thread_id()); 582 } 583 584 void AudioStream::UpdatePlaybackRateIfNeeded() { 585 AssertIsOnAudioThread(); 586 if (mAudioClock.GetPreservesPitch() == mPreservesPitch && 587 mAudioClock.GetPlaybackRate() == mPlaybackRate) { 588 return; 589 } 590 591 EnsureTimeStretcherInitialized(); 592 593 mAudioClock.SetPlaybackRate(mPlaybackRate); 594 mAudioClock.SetPreservesPitch(mPreservesPitch); 595 596 if (!mTimeStretcher) { 597 return; 598 } 599 600 if (mPreservesPitch) { 601 mTimeStretcher->setTempo(mPlaybackRate); 602 mTimeStretcher->setRate(1.0f); 603 } else { 604 mTimeStretcher->setTempo(1.0f); 605 mTimeStretcher->setRate(mPlaybackRate); 606 } 607 } 608 609 long AudioStream::DataCallback(void* aBuffer, long aFrames) { 610 if (CheckThreadIdChanged() && !mSandboxed) { 611 CallbackThreadRegistry::Get()->Register(mAudioThreadId, 612 "NativeAudioCallback"); 613 } 614 WebCore::DenormalDisabler disabler; 615 if (!mCallbacksStarted) { 616 mCallbacksStarted = true; 617 } 618 619 TRACE_AUDIO_CALLBACK_FRAME_COUNT("AudioStream real-time budget", aFrames, 620 mAudioClock.GetInputRate()); 621 TRACE("AudioStream::DataCallback"); 622 MOZ_ASSERT(mState != SHUTDOWN, "No data callback after shutdown"); 623 624 if (SoftRealTimeLimitReached()) { 625 DemoteThreadFromRealTime(); 626 } 627 628 UpdatePlaybackRateIfNeeded(); 629 630 auto writer = AudioBufferWriter( 631 Span<AudioDataValue>(reinterpret_cast<AudioDataValue*>(aBuffer), 632 mOutChannels * aFrames), 633 mOutChannels, aFrames); 634 635 if (mAudioClock.GetInputRate() == mAudioClock.GetOutputRate()) { 636 GetUnprocessed(writer); 637 } else { 638 GetTimeStretched(writer); 639 } 640 641 // Always send audible frames first, and silent frames later. 642 // Otherwise it will break the assumption of FrameHistory. 643 if (!mDataSource.Ended()) { 644 #ifndef XP_MACOSX 645 MonitorAutoLock mon(mMonitor); 646 #endif 647 mAudioClock.UpdateFrameHistory(aFrames - writer.Available(), 648 writer.Available(), mAudioThreadChanged); 649 if (writer.Available() > 0) { 650 TRACE_COMMENT("AudioStream::DataCallback", "Underrun: %d frames missing", 651 writer.Available()); 652 LOGW("lost %d frames", writer.Available()); 653 writer.WriteZeros(writer.Available()); 654 } 655 } else { 656 // No more new data in the data source, and the drain has completed. We 657 // don't need the time stretcher anymore at this point. 658 if (mTimeStretcher && writer.Available()) { 659 delete mTimeStretcher; 660 mTimeStretcher = nullptr; 661 } 662 #ifndef XP_MACOSX 663 MonitorAutoLock mon(mMonitor); 664 #endif 665 mAudioClock.UpdateFrameHistory(aFrames - writer.Available(), 0, 666 mAudioThreadChanged); 667 } 668 669 mDumpFile.Write(static_cast<const AudioDataValue*>(aBuffer), 670 aFrames * mOutChannels); 671 672 if (!mSandboxed && writer.Available() != 0) { 673 CallbackThreadRegistry::Get()->Unregister(mAudioThreadId); 674 } 675 return aFrames - writer.Available(); 676 } 677 678 void AudioStream::StateCallback(cubeb_state aState) { 679 MOZ_ASSERT(mState != SHUTDOWN, "No state callback after shutdown"); 680 LOG("StateCallback, mState=%d cubeb_state=%d", mState.load(), aState); 681 682 MonitorAutoLock mon(mMonitor); 683 if (aState == CUBEB_STATE_DRAINED) { 684 LOG("Drained"); 685 mState = DRAINED; 686 mPlaybackComplete = true; 687 mEndedPromise.ResolveIfExists(true, __func__); 688 } else if (aState == CUBEB_STATE_ERROR) { 689 LOGE("StateCallback() state %d cubeb error", mState.load()); 690 mState = ERRORED; 691 mPlaybackComplete = true; 692 mEndedPromise.RejectIfExists(NS_ERROR_FAILURE, __func__); 693 } 694 } 695 696 bool AudioStream::IsPlaybackCompleted() const { return mPlaybackComplete; } 697 698 AudioClock::AudioClock(uint32_t aInRate) 699 : mOutRate(aInRate), 700 mInRate(aInRate), 701 mPreservesPitch(true), 702 mFrameHistory(new FrameHistory()) {} 703 704 // Audio thread only 705 void AudioClock::UpdateFrameHistory(uint32_t aServiced, uint32_t aUnderrun, 706 bool aAudioThreadChanged) { 707 #ifdef XP_MACOSX 708 if (aAudioThreadChanged) { 709 mCallbackInfoQueue.ResetProducerThreadId(); 710 } 711 // Flush the local items, if any, and then attempt to enqueue the current 712 // item. This is only a fallback mechanism, under non-critical load this is 713 // just going to enqueue an item in the queue. 714 while (!mAudioThreadCallbackInfo.IsEmpty()) { 715 CallbackInfo& info = mAudioThreadCallbackInfo[0]; 716 // If still full, keep it audio-thread side for now. 717 if (mCallbackInfoQueue.Enqueue(info) != 1) { 718 break; 719 } 720 mAudioThreadCallbackInfo.RemoveElementAt(0); 721 } 722 CallbackInfo info(aServiced, aUnderrun, mOutRate); 723 if (mCallbackInfoQueue.Enqueue(info) != 1) { 724 NS_WARNING( 725 "mCallbackInfoQueue full, storing the values in the audio thread."); 726 mAudioThreadCallbackInfo.AppendElement(info); 727 } 728 #else 729 MutexAutoLock lock(mMutex); 730 mFrameHistory->Append(aServiced, aUnderrun, mOutRate); 731 #endif 732 } 733 734 int64_t AudioClock::GetPositionInFrames(int64_t aFrames) { 735 CheckedInt64 v = UsecsToFrames(GetPosition(aFrames), mInRate); 736 return v.isValid() ? v.value() : -1; 737 } 738 739 int64_t AudioClock::GetPosition(int64_t frames) { 740 #ifdef XP_MACOSX 741 // Dequeue all history info, and apply them before returning the position 742 // based on frame history. 743 CallbackInfo info; 744 while (mCallbackInfoQueue.Dequeue(&info, 1)) { 745 mFrameHistory->Append(info.mServiced, info.mUnderrun, info.mOutputRate); 746 } 747 #else 748 MutexAutoLock lock(mMutex); 749 #endif 750 return mFrameHistory->GetPosition(frames); 751 } 752 753 void AudioClock::SetPlaybackRate(double aPlaybackRate) { 754 mOutRate = static_cast<uint32_t>(mInRate / aPlaybackRate); 755 } 756 757 double AudioClock::GetPlaybackRate() const { 758 return static_cast<double>(mInRate) / mOutRate; 759 } 760 761 void AudioClock::SetPreservesPitch(bool aPreservesPitch) { 762 mPreservesPitch = aPreservesPitch; 763 } 764 765 bool AudioClock::GetPreservesPitch() const { return mPreservesPitch; } 766 767 #undef LOG 768 #undef LOGW 769 #undef LOGE 770 771 } // namespace mozilla