1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/AudioStream.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1144 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 +#include <stdio.h> 1.10 +#include <math.h> 1.11 +#include "prlog.h" 1.12 +#include "prdtoa.h" 1.13 +#include "AudioStream.h" 1.14 +#include "VideoUtils.h" 1.15 +#include "mozilla/Monitor.h" 1.16 +#include "mozilla/Mutex.h" 1.17 +#include <algorithm> 1.18 +#include "mozilla/Preferences.h" 1.19 +#include "soundtouch/SoundTouch.h" 1.20 +#include "Latency.h" 1.21 + 1.22 +namespace mozilla { 1.23 + 1.24 +#ifdef LOG 1.25 +#undef LOG 1.26 +#endif 1.27 + 1.28 +#ifdef PR_LOGGING 1.29 +PRLogModuleInfo* gAudioStreamLog = nullptr; 1.30 +// For simple logs 1.31 +#define LOG(x) PR_LOG(gAudioStreamLog, PR_LOG_DEBUG, x) 1.32 +#else 1.33 +#define LOG(x) 1.34 +#endif 1.35 + 1.36 +/** 1.37 + * When MOZ_DUMP_AUDIO is set in the environment (to anything), 1.38 + * we'll drop a series of files in the current working directory named 1.39 + * dumped-audio-<nnn>.wav, one per AudioStream created, containing 1.40 + * the audio for the stream including any skips due to underruns. 1.41 + */ 1.42 +static int gDumpedAudioCount = 0; 1.43 + 1.44 +#define PREF_VOLUME_SCALE "media.volume_scale" 1.45 +#define PREF_CUBEB_LATENCY "media.cubeb_latency_ms" 1.46 + 1.47 +static const uint32_t CUBEB_NORMAL_LATENCY_MS = 100; 1.48 + 1.49 +StaticMutex AudioStream::sMutex; 1.50 +cubeb* AudioStream::sCubebContext; 1.51 +uint32_t AudioStream::sPreferredSampleRate; 1.52 +double AudioStream::sVolumeScale; 1.53 +uint32_t AudioStream::sCubebLatency; 1.54 +bool AudioStream::sCubebLatencyPrefSet; 1.55 + 1.56 +/*static*/ void AudioStream::PrefChanged(const char* aPref, void* aClosure) 1.57 +{ 1.58 + if (strcmp(aPref, PREF_VOLUME_SCALE) == 0) { 1.59 + nsAdoptingString value = Preferences::GetString(aPref); 1.60 + StaticMutexAutoLock lock(sMutex); 1.61 + if (value.IsEmpty()) { 1.62 + sVolumeScale = 1.0; 1.63 + } else { 1.64 + NS_ConvertUTF16toUTF8 utf8(value); 1.65 + sVolumeScale = std::max<double>(0, PR_strtod(utf8.get(), nullptr)); 1.66 + } 1.67 + } else if (strcmp(aPref, PREF_CUBEB_LATENCY) == 0) { 1.68 + // Arbitrary default stream latency of 100ms. The higher this 1.69 + // value, the longer stream volume changes will take to become 1.70 + // audible. 1.71 + sCubebLatencyPrefSet = Preferences::HasUserValue(aPref); 1.72 + uint32_t value = Preferences::GetUint(aPref, CUBEB_NORMAL_LATENCY_MS); 1.73 + StaticMutexAutoLock lock(sMutex); 1.74 + sCubebLatency = std::min<uint32_t>(std::max<uint32_t>(value, 1), 1000); 1.75 + } 1.76 +} 1.77 + 1.78 +/*static*/ double AudioStream::GetVolumeScale() 1.79 +{ 1.80 + StaticMutexAutoLock lock(sMutex); 1.81 + return sVolumeScale; 1.82 +} 1.83 + 1.84 +/*static*/ cubeb* AudioStream::GetCubebContext() 1.85 +{ 1.86 + StaticMutexAutoLock lock(sMutex); 1.87 + return GetCubebContextUnlocked(); 1.88 +} 1.89 + 1.90 +/*static*/ void AudioStream::InitPreferredSampleRate() 1.91 +{ 1.92 + StaticMutexAutoLock lock(sMutex); 1.93 + if (sPreferredSampleRate == 0 && 1.94 + cubeb_get_preferred_sample_rate(GetCubebContextUnlocked(), 1.95 + &sPreferredSampleRate) != CUBEB_OK) { 1.96 + sPreferredSampleRate = 44100; 1.97 + } 1.98 +} 1.99 + 1.100 +/*static*/ cubeb* AudioStream::GetCubebContextUnlocked() 1.101 +{ 1.102 + sMutex.AssertCurrentThreadOwns(); 1.103 + if (sCubebContext || 1.104 + cubeb_init(&sCubebContext, "AudioStream") == CUBEB_OK) { 1.105 + return sCubebContext; 1.106 + } 1.107 + NS_WARNING("cubeb_init failed"); 1.108 + return nullptr; 1.109 +} 1.110 + 1.111 +/*static*/ uint32_t AudioStream::GetCubebLatency() 1.112 +{ 1.113 + StaticMutexAutoLock lock(sMutex); 1.114 + return sCubebLatency; 1.115 +} 1.116 + 1.117 +/*static*/ bool AudioStream::CubebLatencyPrefSet() 1.118 +{ 1.119 + StaticMutexAutoLock lock(sMutex); 1.120 + return sCubebLatencyPrefSet; 1.121 +} 1.122 + 1.123 +#if defined(__ANDROID__) && defined(MOZ_B2G) 1.124 +static cubeb_stream_type ConvertChannelToCubebType(dom::AudioChannel aChannel) 1.125 +{ 1.126 + switch(aChannel) { 1.127 + case dom::AudioChannel::Normal: 1.128 + return CUBEB_STREAM_TYPE_SYSTEM; 1.129 + case dom::AudioChannel::Content: 1.130 + return CUBEB_STREAM_TYPE_MUSIC; 1.131 + case dom::AudioChannel::Notification: 1.132 + return CUBEB_STREAM_TYPE_NOTIFICATION; 1.133 + case dom::AudioChannel::Alarm: 1.134 + return CUBEB_STREAM_TYPE_ALARM; 1.135 + case dom::AudioChannel::Telephony: 1.136 + return CUBEB_STREAM_TYPE_VOICE_CALL; 1.137 + case dom::AudioChannel::Ringer: 1.138 + return CUBEB_STREAM_TYPE_RING; 1.139 + // Currently Android openSLES library doesn't support FORCE_AUDIBLE yet. 1.140 + case dom::AudioChannel::Publicnotification: 1.141 + default: 1.142 + NS_ERROR("The value of AudioChannel is invalid"); 1.143 + return CUBEB_STREAM_TYPE_MAX; 1.144 + } 1.145 +} 1.146 +#endif 1.147 + 1.148 +AudioStream::AudioStream() 1.149 + : mMonitor("AudioStream") 1.150 + , mInRate(0) 1.151 + , mOutRate(0) 1.152 + , mChannels(0) 1.153 + , mOutChannels(0) 1.154 + , mWritten(0) 1.155 + , mAudioClock(MOZ_THIS_IN_INITIALIZER_LIST()) 1.156 + , mLatencyRequest(HighLatency) 1.157 + , mReadPoint(0) 1.158 + , mLostFrames(0) 1.159 + , mDumpFile(nullptr) 1.160 + , mVolume(1.0) 1.161 + , mBytesPerFrame(0) 1.162 + , mState(INITIALIZED) 1.163 + , mNeedsStart(false) 1.164 +{ 1.165 + // keep a ref in case we shut down later than nsLayoutStatics 1.166 + mLatencyLog = AsyncLatencyLogger::Get(true); 1.167 +} 1.168 + 1.169 +AudioStream::~AudioStream() 1.170 +{ 1.171 + LOG(("AudioStream: delete %p, state %d", this, mState)); 1.172 + Shutdown(); 1.173 + if (mDumpFile) { 1.174 + fclose(mDumpFile); 1.175 + } 1.176 +} 1.177 + 1.178 +size_t 1.179 +AudioStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const 1.180 +{ 1.181 + size_t amount = aMallocSizeOf(this); 1.182 + 1.183 + // Possibly add in the future: 1.184 + // - mTimeStretcher 1.185 + // - mLatencyLog 1.186 + // - mCubebStream 1.187 + 1.188 + amount += mInserts.SizeOfExcludingThis(aMallocSizeOf); 1.189 + amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf); 1.190 + 1.191 + return amount; 1.192 +} 1.193 + 1.194 +/*static*/ void AudioStream::InitLibrary() 1.195 +{ 1.196 +#ifdef PR_LOGGING 1.197 + gAudioStreamLog = PR_NewLogModule("AudioStream"); 1.198 +#endif 1.199 + PrefChanged(PREF_VOLUME_SCALE, nullptr); 1.200 + Preferences::RegisterCallback(PrefChanged, PREF_VOLUME_SCALE); 1.201 + PrefChanged(PREF_CUBEB_LATENCY, nullptr); 1.202 + Preferences::RegisterCallback(PrefChanged, PREF_CUBEB_LATENCY); 1.203 +} 1.204 + 1.205 +/*static*/ void AudioStream::ShutdownLibrary() 1.206 +{ 1.207 + Preferences::UnregisterCallback(PrefChanged, PREF_VOLUME_SCALE); 1.208 + Preferences::UnregisterCallback(PrefChanged, PREF_CUBEB_LATENCY); 1.209 + 1.210 + StaticMutexAutoLock lock(sMutex); 1.211 + if (sCubebContext) { 1.212 + cubeb_destroy(sCubebContext); 1.213 + sCubebContext = nullptr; 1.214 + } 1.215 +} 1.216 + 1.217 +nsresult AudioStream::EnsureTimeStretcherInitializedUnlocked() 1.218 +{ 1.219 + mMonitor.AssertCurrentThreadOwns(); 1.220 + if (!mTimeStretcher) { 1.221 + mTimeStretcher = new soundtouch::SoundTouch(); 1.222 + mTimeStretcher->setSampleRate(mInRate); 1.223 + mTimeStretcher->setChannels(mOutChannels); 1.224 + mTimeStretcher->setPitch(1.0); 1.225 + } 1.226 + return NS_OK; 1.227 +} 1.228 + 1.229 +nsresult AudioStream::SetPlaybackRate(double aPlaybackRate) 1.230 +{ 1.231 + NS_ASSERTION(aPlaybackRate > 0.0, 1.232 + "Can't handle negative or null playbackrate in the AudioStream."); 1.233 + // Avoid instantiating the resampler if we are not changing the playback rate. 1.234 + // GetPreservesPitch/SetPreservesPitch don't need locking before calling 1.235 + if (aPlaybackRate == mAudioClock.GetPlaybackRate()) { 1.236 + return NS_OK; 1.237 + } 1.238 + 1.239 + // MUST lock since the rate transposer is used from the cubeb callback, 1.240 + // and rate changes can cause the buffer to be reallocated 1.241 + MonitorAutoLock mon(mMonitor); 1.242 + if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) { 1.243 + return NS_ERROR_FAILURE; 1.244 + } 1.245 + 1.246 + mAudioClock.SetPlaybackRateUnlocked(aPlaybackRate); 1.247 + mOutRate = mInRate / aPlaybackRate; 1.248 + 1.249 + if (mAudioClock.GetPreservesPitch()) { 1.250 + mTimeStretcher->setTempo(aPlaybackRate); 1.251 + mTimeStretcher->setRate(1.0f); 1.252 + } else { 1.253 + mTimeStretcher->setTempo(1.0f); 1.254 + mTimeStretcher->setRate(aPlaybackRate); 1.255 + } 1.256 + return NS_OK; 1.257 +} 1.258 + 1.259 +nsresult AudioStream::SetPreservesPitch(bool aPreservesPitch) 1.260 +{ 1.261 + // Avoid instantiating the timestretcher instance if not needed. 1.262 + if (aPreservesPitch == mAudioClock.GetPreservesPitch()) { 1.263 + return NS_OK; 1.264 + } 1.265 + 1.266 + // MUST lock since the rate transposer is used from the cubeb callback, 1.267 + // and rate changes can cause the buffer to be reallocated 1.268 + MonitorAutoLock mon(mMonitor); 1.269 + if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) { 1.270 + return NS_ERROR_FAILURE; 1.271 + } 1.272 + 1.273 + if (aPreservesPitch == true) { 1.274 + mTimeStretcher->setTempo(mAudioClock.GetPlaybackRate()); 1.275 + mTimeStretcher->setRate(1.0f); 1.276 + } else { 1.277 + mTimeStretcher->setTempo(1.0f); 1.278 + mTimeStretcher->setRate(mAudioClock.GetPlaybackRate()); 1.279 + } 1.280 + 1.281 + mAudioClock.SetPreservesPitch(aPreservesPitch); 1.282 + 1.283 + return NS_OK; 1.284 +} 1.285 + 1.286 +int64_t AudioStream::GetWritten() 1.287 +{ 1.288 + return mWritten; 1.289 +} 1.290 + 1.291 +/*static*/ int AudioStream::MaxNumberOfChannels() 1.292 +{ 1.293 + cubeb* cubebContext = GetCubebContext(); 1.294 + uint32_t maxNumberOfChannels; 1.295 + if (cubebContext && 1.296 + cubeb_get_max_channel_count(cubebContext, 1.297 + &maxNumberOfChannels) == CUBEB_OK) { 1.298 + return static_cast<int>(maxNumberOfChannels); 1.299 + } 1.300 + 1.301 + return 0; 1.302 +} 1.303 + 1.304 +/*static*/ int AudioStream::PreferredSampleRate() 1.305 +{ 1.306 + MOZ_ASSERT(sPreferredSampleRate, 1.307 + "sPreferredSampleRate has not been initialized!"); 1.308 + return sPreferredSampleRate; 1.309 +} 1.310 + 1.311 +static void SetUint16LE(uint8_t* aDest, uint16_t aValue) 1.312 +{ 1.313 + aDest[0] = aValue & 0xFF; 1.314 + aDest[1] = aValue >> 8; 1.315 +} 1.316 + 1.317 +static void SetUint32LE(uint8_t* aDest, uint32_t aValue) 1.318 +{ 1.319 + SetUint16LE(aDest, aValue & 0xFFFF); 1.320 + SetUint16LE(aDest + 2, aValue >> 16); 1.321 +} 1.322 + 1.323 +static FILE* 1.324 +OpenDumpFile(AudioStream* aStream) 1.325 +{ 1.326 + if (!getenv("MOZ_DUMP_AUDIO")) 1.327 + return nullptr; 1.328 + char buf[100]; 1.329 + sprintf(buf, "dumped-audio-%d.wav", gDumpedAudioCount); 1.330 + FILE* f = fopen(buf, "wb"); 1.331 + if (!f) 1.332 + return nullptr; 1.333 + ++gDumpedAudioCount; 1.334 + 1.335 + uint8_t header[] = { 1.336 + // RIFF header 1.337 + 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 1.338 + // fmt chunk. We always write 16-bit samples. 1.339 + 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFF, 0xFF, 1.340 + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x10, 0x00, 1.341 + // data chunk 1.342 + 0x64, 0x61, 0x74, 0x61, 0xFE, 0xFF, 0xFF, 0x7F 1.343 + }; 1.344 + static const int CHANNEL_OFFSET = 22; 1.345 + static const int SAMPLE_RATE_OFFSET = 24; 1.346 + static const int BLOCK_ALIGN_OFFSET = 32; 1.347 + SetUint16LE(header + CHANNEL_OFFSET, aStream->GetChannels()); 1.348 + SetUint32LE(header + SAMPLE_RATE_OFFSET, aStream->GetRate()); 1.349 + SetUint16LE(header + BLOCK_ALIGN_OFFSET, aStream->GetChannels()*2); 1.350 + fwrite(header, sizeof(header), 1, f); 1.351 + 1.352 + return f; 1.353 +} 1.354 + 1.355 +static void 1.356 +WriteDumpFile(FILE* aDumpFile, AudioStream* aStream, uint32_t aFrames, 1.357 + void* aBuffer) 1.358 +{ 1.359 + if (!aDumpFile) 1.360 + return; 1.361 + 1.362 + uint32_t samples = aStream->GetOutChannels()*aFrames; 1.363 + if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) { 1.364 + fwrite(aBuffer, 2, samples, aDumpFile); 1.365 + return; 1.366 + } 1.367 + 1.368 + NS_ASSERTION(AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_FLOAT32, "bad format"); 1.369 + nsAutoTArray<uint8_t, 1024*2> buf; 1.370 + buf.SetLength(samples*2); 1.371 + float* input = static_cast<float*>(aBuffer); 1.372 + uint8_t* output = buf.Elements(); 1.373 + for (uint32_t i = 0; i < samples; ++i) { 1.374 + SetUint16LE(output + i*2, int16_t(input[i]*32767.0f)); 1.375 + } 1.376 + fwrite(output, 2, samples, aDumpFile); 1.377 + fflush(aDumpFile); 1.378 +} 1.379 + 1.380 +// NOTE: this must not block a LowLatency stream for any significant amount 1.381 +// of time, or it will block the entirety of MSG 1.382 +nsresult 1.383 +AudioStream::Init(int32_t aNumChannels, int32_t aRate, 1.384 + const dom::AudioChannel aAudioChannel, 1.385 + LatencyRequest aLatencyRequest) 1.386 +{ 1.387 + if (!GetCubebContext() || aNumChannels < 0 || aRate < 0) { 1.388 + return NS_ERROR_FAILURE; 1.389 + } 1.390 + 1.391 + PR_LOG(gAudioStreamLog, PR_LOG_DEBUG, 1.392 + ("%s channels: %d, rate: %d for %p", __FUNCTION__, aNumChannels, aRate, this)); 1.393 + mInRate = mOutRate = aRate; 1.394 + mChannels = aNumChannels; 1.395 + mOutChannels = (aNumChannels > 2) ? 2 : aNumChannels; 1.396 + mLatencyRequest = aLatencyRequest; 1.397 + 1.398 + mDumpFile = OpenDumpFile(this); 1.399 + 1.400 + cubeb_stream_params params; 1.401 + params.rate = aRate; 1.402 + params.channels = mOutChannels; 1.403 +#if defined(__ANDROID__) 1.404 +#if defined(MOZ_B2G) 1.405 + params.stream_type = ConvertChannelToCubebType(aAudioChannel); 1.406 +#else 1.407 + params.stream_type = CUBEB_STREAM_TYPE_MUSIC; 1.408 +#endif 1.409 + 1.410 + if (params.stream_type == CUBEB_STREAM_TYPE_MAX) { 1.411 + return NS_ERROR_INVALID_ARG; 1.412 + } 1.413 +#endif 1.414 + if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) { 1.415 + params.format = CUBEB_SAMPLE_S16NE; 1.416 + } else { 1.417 + params.format = CUBEB_SAMPLE_FLOAT32NE; 1.418 + } 1.419 + mBytesPerFrame = sizeof(AudioDataValue) * mOutChannels; 1.420 + 1.421 + mAudioClock.Init(); 1.422 + 1.423 + // Size mBuffer for one second of audio. This value is arbitrary, and was 1.424 + // selected based on the observed behaviour of the existing AudioStream 1.425 + // implementations. 1.426 + uint32_t bufferLimit = FramesToBytes(aRate); 1.427 + NS_ABORT_IF_FALSE(bufferLimit % mBytesPerFrame == 0, "Must buffer complete frames"); 1.428 + mBuffer.SetCapacity(bufferLimit); 1.429 + 1.430 + if (aLatencyRequest == LowLatency) { 1.431 + // Don't block this thread to initialize a cubeb stream. 1.432 + // When this is done, it will start callbacks from Cubeb. Those will 1.433 + // cause us to move from INITIALIZED to RUNNING. Until then, we 1.434 + // can't access any cubeb functions. 1.435 + // Use a RefPtr to avoid leaks if Dispatch fails 1.436 + RefPtr<AudioInitTask> init = new AudioInitTask(this, aLatencyRequest, params); 1.437 + init->Dispatch(); 1.438 + return NS_OK; 1.439 + } 1.440 + // High latency - open synchronously 1.441 + nsresult rv = OpenCubeb(params, aLatencyRequest); 1.442 + // See if we need to start() the stream, since we must do that from this 1.443 + // thread for now (cubeb API issue) 1.444 + CheckForStart(); 1.445 + return rv; 1.446 +} 1.447 + 1.448 +// This code used to live inside AudioStream::Init(), but on Mac (others?) 1.449 +// it has been known to take 300-800 (or even 8500) ms to execute(!) 1.450 +nsresult 1.451 +AudioStream::OpenCubeb(cubeb_stream_params &aParams, 1.452 + LatencyRequest aLatencyRequest) 1.453 +{ 1.454 + cubeb* cubebContext = GetCubebContext(); 1.455 + if (!cubebContext) { 1.456 + MonitorAutoLock mon(mMonitor); 1.457 + mState = AudioStream::ERRORED; 1.458 + return NS_ERROR_FAILURE; 1.459 + } 1.460 + 1.461 + // If the latency pref is set, use it. Otherwise, if this stream is intended 1.462 + // for low latency playback, try to get the lowest latency possible. 1.463 + // Otherwise, for normal streams, use 100ms. 1.464 + uint32_t latency; 1.465 + if (aLatencyRequest == LowLatency && !CubebLatencyPrefSet()) { 1.466 + if (cubeb_get_min_latency(cubebContext, aParams, &latency) != CUBEB_OK) { 1.467 + latency = GetCubebLatency(); 1.468 + } 1.469 + } else { 1.470 + latency = GetCubebLatency(); 1.471 + } 1.472 + 1.473 + { 1.474 + cubeb_stream* stream; 1.475 + if (cubeb_stream_init(cubebContext, &stream, "AudioStream", aParams, 1.476 + latency, DataCallback_S, StateCallback_S, this) == CUBEB_OK) { 1.477 + MonitorAutoLock mon(mMonitor); 1.478 + mCubebStream.own(stream); 1.479 + // Make sure we weren't shut down while in flight! 1.480 + if (mState == SHUTDOWN) { 1.481 + mCubebStream.reset(); 1.482 + LOG(("AudioStream::OpenCubeb() %p Shutdown while opening cubeb", this)); 1.483 + return NS_ERROR_FAILURE; 1.484 + } 1.485 + 1.486 + // We can't cubeb_stream_start() the thread from a transient thread due to 1.487 + // cubeb API requirements (init can be called from another thread, but 1.488 + // not start/stop/destroy/etc) 1.489 + } else { 1.490 + MonitorAutoLock mon(mMonitor); 1.491 + mState = ERRORED; 1.492 + LOG(("AudioStream::OpenCubeb() %p failed to init cubeb", this)); 1.493 + return NS_ERROR_FAILURE; 1.494 + } 1.495 + } 1.496 + 1.497 + return NS_OK; 1.498 +} 1.499 + 1.500 +void 1.501 +AudioStream::CheckForStart() 1.502 +{ 1.503 + if (mState == INITIALIZED) { 1.504 + // Start the stream right away when low latency has been requested. This means 1.505 + // that the DataCallback will feed silence to cubeb, until the first frames 1.506 + // are written to this AudioStream. Also start if a start has been queued. 1.507 + if (mLatencyRequest == LowLatency || mNeedsStart) { 1.508 + StartUnlocked(); // mState = STARTED or ERRORED 1.509 + mNeedsStart = false; 1.510 + PR_LOG(gAudioStreamLog, PR_LOG_WARNING, 1.511 + ("Started waiting %s-latency stream", 1.512 + mLatencyRequest == LowLatency ? "low" : "high")); 1.513 + } else { 1.514 + // high latency, not full - OR Pause() was called before we got here 1.515 + PR_LOG(gAudioStreamLog, PR_LOG_DEBUG, 1.516 + ("Not starting waiting %s-latency stream", 1.517 + mLatencyRequest == LowLatency ? "low" : "high")); 1.518 + } 1.519 + } 1.520 +} 1.521 + 1.522 +NS_IMETHODIMP 1.523 +AudioInitTask::Run() 1.524 +{ 1.525 + MOZ_ASSERT(mThread); 1.526 + if (NS_IsMainThread()) { 1.527 + mThread->Shutdown(); // can't Shutdown from the thread itself, darn 1.528 + // Don't null out mThread! 1.529 + // See bug 999104. We must hold a ref to the thread across Dispatch() 1.530 + // since the internal mThread ref could be released while processing 1.531 + // the Dispatch(), and Dispatch/PutEvent itself doesn't hold a ref; it 1.532 + // assumes the caller does. 1.533 + return NS_OK; 1.534 + } 1.535 + 1.536 + nsresult rv = mAudioStream->OpenCubeb(mParams, mLatencyRequest); 1.537 + 1.538 + // and now kill this thread 1.539 + NS_DispatchToMainThread(this); 1.540 + return rv; 1.541 +} 1.542 + 1.543 +// aTime is the time in ms the samples were inserted into MediaStreamGraph 1.544 +nsresult 1.545 +AudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames, TimeStamp *aTime) 1.546 +{ 1.547 + MonitorAutoLock mon(mMonitor); 1.548 + if (mState == ERRORED) { 1.549 + return NS_ERROR_FAILURE; 1.550 + } 1.551 + NS_ASSERTION(mState == INITIALIZED || mState == STARTED || mState == RUNNING, 1.552 + "Stream write in unexpected state."); 1.553 + 1.554 + // See if we need to start() the stream, since we must do that from this thread 1.555 + CheckForStart(); 1.556 + 1.557 + // Downmix to Stereo. 1.558 + if (mChannels > 2 && mChannels <= 8) { 1.559 + DownmixAudioToStereo(const_cast<AudioDataValue*> (aBuf), mChannels, aFrames); 1.560 + } 1.561 + else if (mChannels > 8) { 1.562 + return NS_ERROR_FAILURE; 1.563 + } 1.564 + 1.565 + const uint8_t* src = reinterpret_cast<const uint8_t*>(aBuf); 1.566 + uint32_t bytesToCopy = FramesToBytes(aFrames); 1.567 + 1.568 + // XXX this will need to change if we want to enable this on-the-fly! 1.569 + if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG)) { 1.570 + // Record the position and time this data was inserted 1.571 + int64_t timeMs; 1.572 + if (aTime && !aTime->IsNull()) { 1.573 + if (mStartTime.IsNull()) { 1.574 + AsyncLatencyLogger::Get(true)->GetStartTime(mStartTime); 1.575 + } 1.576 + timeMs = (*aTime - mStartTime).ToMilliseconds(); 1.577 + } else { 1.578 + timeMs = 0; 1.579 + } 1.580 + struct Inserts insert = { timeMs, aFrames}; 1.581 + mInserts.AppendElement(insert); 1.582 + } 1.583 + 1.584 + while (bytesToCopy > 0) { 1.585 + uint32_t available = std::min(bytesToCopy, mBuffer.Available()); 1.586 + NS_ABORT_IF_FALSE(available % mBytesPerFrame == 0, 1.587 + "Must copy complete frames."); 1.588 + 1.589 + mBuffer.AppendElements(src, available); 1.590 + src += available; 1.591 + bytesToCopy -= available; 1.592 + 1.593 + if (bytesToCopy > 0) { 1.594 + // Careful - the CubebInit thread may not have gotten to STARTED yet 1.595 + if ((mState == INITIALIZED || mState == STARTED) && mLatencyRequest == LowLatency) { 1.596 + // don't ever block MediaStreamGraph low-latency streams 1.597 + uint32_t remains = 0; // we presume the buffer is full 1.598 + if (mBuffer.Length() > bytesToCopy) { 1.599 + remains = mBuffer.Length() - bytesToCopy; // Free up just enough space 1.600 + } 1.601 + // account for dropping samples 1.602 + PR_LOG(gAudioStreamLog, PR_LOG_WARNING, ("Stream %p dropping %u bytes (%u frames)in Write()", 1.603 + this, mBuffer.Length() - remains, BytesToFrames(mBuffer.Length() - remains))); 1.604 + mReadPoint += BytesToFrames(mBuffer.Length() - remains); 1.605 + mBuffer.ContractTo(remains); 1.606 + } else { // RUNNING or high latency 1.607 + // If we are not playing, but our buffer is full, start playing to make 1.608 + // room for soon-to-be-decoded data. 1.609 + if (mState != STARTED && mState != RUNNING) { 1.610 + PR_LOG(gAudioStreamLog, PR_LOG_WARNING, ("Starting stream %p in Write (%u waiting)", 1.611 + this, bytesToCopy)); 1.612 + StartUnlocked(); 1.613 + if (mState == ERRORED) { 1.614 + return NS_ERROR_FAILURE; 1.615 + } 1.616 + } 1.617 + PR_LOG(gAudioStreamLog, PR_LOG_WARNING, ("Stream %p waiting in Write() (%u waiting)", 1.618 + this, bytesToCopy)); 1.619 + mon.Wait(); 1.620 + } 1.621 + } 1.622 + } 1.623 + 1.624 + mWritten += aFrames; 1.625 + return NS_OK; 1.626 +} 1.627 + 1.628 +uint32_t 1.629 +AudioStream::Available() 1.630 +{ 1.631 + MonitorAutoLock mon(mMonitor); 1.632 + NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Buffer invariant violated."); 1.633 + return BytesToFrames(mBuffer.Available()); 1.634 +} 1.635 + 1.636 +void 1.637 +AudioStream::SetVolume(double aVolume) 1.638 +{ 1.639 + MonitorAutoLock mon(mMonitor); 1.640 + NS_ABORT_IF_FALSE(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume"); 1.641 + mVolume = aVolume; 1.642 +} 1.643 + 1.644 +void 1.645 +AudioStream::Drain() 1.646 +{ 1.647 + MonitorAutoLock mon(mMonitor); 1.648 + LOG(("AudioStream::Drain() for %p, state %d, avail %u", this, mState, mBuffer.Available())); 1.649 + if (mState != STARTED && mState != RUNNING) { 1.650 + NS_ASSERTION(mState == ERRORED || mBuffer.Available() == 0, "Draining without full buffer of unplayed audio"); 1.651 + return; 1.652 + } 1.653 + mState = DRAINING; 1.654 + while (mState == DRAINING) { 1.655 + mon.Wait(); 1.656 + } 1.657 +} 1.658 + 1.659 +void 1.660 +AudioStream::Start() 1.661 +{ 1.662 + MonitorAutoLock mon(mMonitor); 1.663 + StartUnlocked(); 1.664 +} 1.665 + 1.666 +void 1.667 +AudioStream::StartUnlocked() 1.668 +{ 1.669 + mMonitor.AssertCurrentThreadOwns(); 1.670 + if (!mCubebStream) { 1.671 + mNeedsStart = true; 1.672 + return; 1.673 + } 1.674 + MonitorAutoUnlock mon(mMonitor); 1.675 + if (mState == INITIALIZED) { 1.676 + int r = cubeb_stream_start(mCubebStream); 1.677 + mState = r == CUBEB_OK ? STARTED : ERRORED; 1.678 + LOG(("AudioStream: started %p, state %s", this, mState == STARTED ? "STARTED" : "ERRORED")); 1.679 + } 1.680 +} 1.681 + 1.682 +void 1.683 +AudioStream::Pause() 1.684 +{ 1.685 + MonitorAutoLock mon(mMonitor); 1.686 + if (!mCubebStream || (mState != STARTED && mState != RUNNING)) { 1.687 + mNeedsStart = false; 1.688 + mState = STOPPED; // which also tells async OpenCubeb not to start, just init 1.689 + return; 1.690 + } 1.691 + 1.692 + int r; 1.693 + { 1.694 + MonitorAutoUnlock mon(mMonitor); 1.695 + r = cubeb_stream_stop(mCubebStream); 1.696 + } 1.697 + if (mState != ERRORED && r == CUBEB_OK) { 1.698 + mState = STOPPED; 1.699 + } 1.700 +} 1.701 + 1.702 +void 1.703 +AudioStream::Resume() 1.704 +{ 1.705 + MonitorAutoLock mon(mMonitor); 1.706 + if (!mCubebStream || mState != STOPPED) { 1.707 + return; 1.708 + } 1.709 + 1.710 + int r; 1.711 + { 1.712 + MonitorAutoUnlock mon(mMonitor); 1.713 + r = cubeb_stream_start(mCubebStream); 1.714 + } 1.715 + if (mState != ERRORED && r == CUBEB_OK) { 1.716 + mState = STARTED; 1.717 + } 1.718 +} 1.719 + 1.720 +void 1.721 +AudioStream::Shutdown() 1.722 +{ 1.723 + LOG(("AudioStream: Shutdown %p, state %d", this, mState)); 1.724 + { 1.725 + MonitorAutoLock mon(mMonitor); 1.726 + if (mState == STARTED || mState == RUNNING) { 1.727 + MonitorAutoUnlock mon(mMonitor); 1.728 + Pause(); 1.729 + } 1.730 + MOZ_ASSERT(mState != STARTED && mState != RUNNING); // paranoia 1.731 + mState = SHUTDOWN; 1.732 + } 1.733 + // Must not try to shut down cubeb from within the lock! wasapi may still 1.734 + // call our callback after Pause()/stop()!?! Bug 996162 1.735 + if (mCubebStream) { 1.736 + mCubebStream.reset(); 1.737 + } 1.738 +} 1.739 + 1.740 +int64_t 1.741 +AudioStream::GetPosition() 1.742 +{ 1.743 + MonitorAutoLock mon(mMonitor); 1.744 + return mAudioClock.GetPositionUnlocked(); 1.745 +} 1.746 + 1.747 +// This function is miscompiled by PGO with MSVC 2010. See bug 768333. 1.748 +#ifdef _MSC_VER 1.749 +#pragma optimize("", off) 1.750 +#endif 1.751 +int64_t 1.752 +AudioStream::GetPositionInFrames() 1.753 +{ 1.754 + return mAudioClock.GetPositionInFrames(); 1.755 +} 1.756 +#ifdef _MSC_VER 1.757 +#pragma optimize("", on) 1.758 +#endif 1.759 + 1.760 +int64_t 1.761 +AudioStream::GetPositionInFramesInternal() 1.762 +{ 1.763 + MonitorAutoLock mon(mMonitor); 1.764 + return GetPositionInFramesUnlocked(); 1.765 +} 1.766 + 1.767 +int64_t 1.768 +AudioStream::GetPositionInFramesUnlocked() 1.769 +{ 1.770 + mMonitor.AssertCurrentThreadOwns(); 1.771 + 1.772 + if (!mCubebStream || mState == ERRORED) { 1.773 + return -1; 1.774 + } 1.775 + 1.776 + uint64_t position = 0; 1.777 + { 1.778 + MonitorAutoUnlock mon(mMonitor); 1.779 + if (cubeb_stream_get_position(mCubebStream, &position) != CUBEB_OK) { 1.780 + return -1; 1.781 + } 1.782 + } 1.783 + 1.784 + // Adjust the reported position by the number of silent frames written 1.785 + // during stream underruns. 1.786 + uint64_t adjustedPosition = 0; 1.787 + if (position >= mLostFrames) { 1.788 + adjustedPosition = position - mLostFrames; 1.789 + } 1.790 + return std::min<uint64_t>(adjustedPosition, INT64_MAX); 1.791 +} 1.792 + 1.793 +int64_t 1.794 +AudioStream::GetLatencyInFrames() 1.795 +{ 1.796 + uint32_t latency; 1.797 + if (cubeb_stream_get_latency(mCubebStream, &latency)) { 1.798 + NS_WARNING("Could not get cubeb latency."); 1.799 + return 0; 1.800 + } 1.801 + return static_cast<int64_t>(latency); 1.802 +} 1.803 + 1.804 +bool 1.805 +AudioStream::IsPaused() 1.806 +{ 1.807 + MonitorAutoLock mon(mMonitor); 1.808 + return mState == STOPPED; 1.809 +} 1.810 + 1.811 +void 1.812 +AudioStream::GetBufferInsertTime(int64_t &aTimeMs) 1.813 +{ 1.814 + if (mInserts.Length() > 0) { 1.815 + // Find the right block, but don't leave the array empty 1.816 + while (mInserts.Length() > 1 && mReadPoint >= mInserts[0].mFrames) { 1.817 + mReadPoint -= mInserts[0].mFrames; 1.818 + mInserts.RemoveElementAt(0); 1.819 + } 1.820 + // offset for amount already read 1.821 + // XXX Note: could misreport if we couldn't find a block in the right timeframe 1.822 + aTimeMs = mInserts[0].mTimeMs + ((mReadPoint * 1000) / mOutRate); 1.823 + } else { 1.824 + aTimeMs = INT64_MAX; 1.825 + } 1.826 +} 1.827 + 1.828 +long 1.829 +AudioStream::GetUnprocessed(void* aBuffer, long aFrames, int64_t &aTimeMs) 1.830 +{ 1.831 + uint8_t* wpos = reinterpret_cast<uint8_t*>(aBuffer); 1.832 + 1.833 + // Flush the timestretcher pipeline, if we were playing using a playback rate 1.834 + // other than 1.0. 1.835 + uint32_t flushedFrames = 0; 1.836 + if (mTimeStretcher && mTimeStretcher->numSamples()) { 1.837 + flushedFrames = mTimeStretcher->receiveSamples(reinterpret_cast<AudioDataValue*>(wpos), aFrames); 1.838 + wpos += FramesToBytes(flushedFrames); 1.839 + } 1.840 + uint32_t toPopBytes = FramesToBytes(aFrames - flushedFrames); 1.841 + uint32_t available = std::min(toPopBytes, mBuffer.Length()); 1.842 + 1.843 + void* input[2]; 1.844 + uint32_t input_size[2]; 1.845 + mBuffer.PopElements(available, &input[0], &input_size[0], &input[1], &input_size[1]); 1.846 + memcpy(wpos, input[0], input_size[0]); 1.847 + wpos += input_size[0]; 1.848 + memcpy(wpos, input[1], input_size[1]); 1.849 + 1.850 + // First time block now has our first returned sample 1.851 + mReadPoint += BytesToFrames(available); 1.852 + GetBufferInsertTime(aTimeMs); 1.853 + 1.854 + return BytesToFrames(available) + flushedFrames; 1.855 +} 1.856 + 1.857 +// Get unprocessed samples, and pad the beginning of the buffer with silence if 1.858 +// there is not enough data. 1.859 +long 1.860 +AudioStream::GetUnprocessedWithSilencePadding(void* aBuffer, long aFrames, int64_t& aTimeMs) 1.861 +{ 1.862 + uint32_t toPopBytes = FramesToBytes(aFrames); 1.863 + uint32_t available = std::min(toPopBytes, mBuffer.Length()); 1.864 + uint32_t silenceOffset = toPopBytes - available; 1.865 + 1.866 + uint8_t* wpos = reinterpret_cast<uint8_t*>(aBuffer); 1.867 + 1.868 + memset(wpos, 0, silenceOffset); 1.869 + wpos += silenceOffset; 1.870 + 1.871 + void* input[2]; 1.872 + uint32_t input_size[2]; 1.873 + mBuffer.PopElements(available, &input[0], &input_size[0], &input[1], &input_size[1]); 1.874 + memcpy(wpos, input[0], input_size[0]); 1.875 + wpos += input_size[0]; 1.876 + memcpy(wpos, input[1], input_size[1]); 1.877 + 1.878 + GetBufferInsertTime(aTimeMs); 1.879 + 1.880 + return aFrames; 1.881 +} 1.882 + 1.883 +long 1.884 +AudioStream::GetTimeStretched(void* aBuffer, long aFrames, int64_t &aTimeMs) 1.885 +{ 1.886 + long processedFrames = 0; 1.887 + 1.888 + // We need to call the non-locking version, because we already have the lock. 1.889 + if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) { 1.890 + return 0; 1.891 + } 1.892 + 1.893 + uint8_t* wpos = reinterpret_cast<uint8_t*>(aBuffer); 1.894 + double playbackRate = static_cast<double>(mInRate) / mOutRate; 1.895 + uint32_t toPopBytes = FramesToBytes(ceil(aFrames / playbackRate)); 1.896 + uint32_t available = 0; 1.897 + bool lowOnBufferedData = false; 1.898 + do { 1.899 + // Check if we already have enough data in the time stretcher pipeline. 1.900 + if (mTimeStretcher->numSamples() <= static_cast<uint32_t>(aFrames)) { 1.901 + void* input[2]; 1.902 + uint32_t input_size[2]; 1.903 + available = std::min(mBuffer.Length(), toPopBytes); 1.904 + if (available != toPopBytes) { 1.905 + lowOnBufferedData = true; 1.906 + } 1.907 + mBuffer.PopElements(available, &input[0], &input_size[0], 1.908 + &input[1], &input_size[1]); 1.909 + mReadPoint += BytesToFrames(available); 1.910 + for(uint32_t i = 0; i < 2; i++) { 1.911 + mTimeStretcher->putSamples(reinterpret_cast<AudioDataValue*>(input[i]), BytesToFrames(input_size[i])); 1.912 + } 1.913 + } 1.914 + uint32_t receivedFrames = mTimeStretcher->receiveSamples(reinterpret_cast<AudioDataValue*>(wpos), aFrames - processedFrames); 1.915 + wpos += FramesToBytes(receivedFrames); 1.916 + processedFrames += receivedFrames; 1.917 + } while (processedFrames < aFrames && !lowOnBufferedData); 1.918 + 1.919 + GetBufferInsertTime(aTimeMs); 1.920 + 1.921 + return processedFrames; 1.922 +} 1.923 + 1.924 +long 1.925 +AudioStream::DataCallback(void* aBuffer, long aFrames) 1.926 +{ 1.927 + MonitorAutoLock mon(mMonitor); 1.928 + uint32_t available = std::min(static_cast<uint32_t>(FramesToBytes(aFrames)), mBuffer.Length()); 1.929 + NS_ABORT_IF_FALSE(available % mBytesPerFrame == 0, "Must copy complete frames"); 1.930 + AudioDataValue* output = reinterpret_cast<AudioDataValue*>(aBuffer); 1.931 + uint32_t underrunFrames = 0; 1.932 + uint32_t servicedFrames = 0; 1.933 + int64_t insertTime; 1.934 + 1.935 + // NOTE: wasapi (others?) can call us back *after* stop()/Shutdown() (mState == SHUTDOWN) 1.936 + // Bug 996162 1.937 + 1.938 + // callback tells us cubeb succeeded initializing 1.939 + if (mState == STARTED) { 1.940 + // For low-latency streams, we want to minimize any built-up data when 1.941 + // we start getting callbacks. 1.942 + // Simple version - contract on first callback only. 1.943 + if (mLatencyRequest == LowLatency) { 1.944 +#ifdef PR_LOGGING 1.945 + uint32_t old_len = mBuffer.Length(); 1.946 +#endif 1.947 + available = mBuffer.ContractTo(FramesToBytes(aFrames)); 1.948 +#ifdef PR_LOGGING 1.949 + TimeStamp now = TimeStamp::Now(); 1.950 + if (!mStartTime.IsNull()) { 1.951 + int64_t timeMs = (now - mStartTime).ToMilliseconds(); 1.952 + PR_LOG(gAudioStreamLog, PR_LOG_WARNING, 1.953 + ("Stream took %lldms to start after first Write() @ %u", timeMs, mOutRate)); 1.954 + } else { 1.955 + PR_LOG(gAudioStreamLog, PR_LOG_WARNING, 1.956 + ("Stream started before Write() @ %u", mOutRate)); 1.957 + } 1.958 + 1.959 + if (old_len != available) { 1.960 + // Note that we may have dropped samples in Write() as well! 1.961 + PR_LOG(gAudioStreamLog, PR_LOG_WARNING, 1.962 + ("AudioStream %p dropped %u + %u initial frames @ %u", this, 1.963 + mReadPoint, BytesToFrames(old_len - available), mOutRate)); 1.964 + mReadPoint += BytesToFrames(old_len - available); 1.965 + } 1.966 +#endif 1.967 + } 1.968 + mState = RUNNING; 1.969 + } 1.970 + 1.971 + if (available) { 1.972 + // When we are playing a low latency stream, and it is the first time we are 1.973 + // getting data from the buffer, we prefer to add the silence for an 1.974 + // underrun at the beginning of the buffer, so the first buffer is not cut 1.975 + // in half by the silence inserted to compensate for the underrun. 1.976 + if (mInRate == mOutRate) { 1.977 + if (mLatencyRequest == LowLatency && !mWritten) { 1.978 + servicedFrames = GetUnprocessedWithSilencePadding(output, aFrames, insertTime); 1.979 + } else { 1.980 + servicedFrames = GetUnprocessed(output, aFrames, insertTime); 1.981 + } 1.982 + } else { 1.983 + servicedFrames = GetTimeStretched(output, aFrames, insertTime); 1.984 + } 1.985 + float scaled_volume = float(GetVolumeScale() * mVolume); 1.986 + 1.987 + ScaleAudioSamples(output, aFrames * mOutChannels, scaled_volume); 1.988 + 1.989 + NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Must copy complete frames"); 1.990 + 1.991 + // Notify any blocked Write() call that more space is available in mBuffer. 1.992 + mon.NotifyAll(); 1.993 + } else { 1.994 + GetBufferInsertTime(insertTime); 1.995 + } 1.996 + 1.997 + underrunFrames = aFrames - servicedFrames; 1.998 + 1.999 + if (mState != DRAINING) { 1.1000 + uint8_t* rpos = static_cast<uint8_t*>(aBuffer) + FramesToBytes(aFrames - underrunFrames); 1.1001 + memset(rpos, 0, FramesToBytes(underrunFrames)); 1.1002 + if (underrunFrames) { 1.1003 + PR_LOG(gAudioStreamLog, PR_LOG_WARNING, 1.1004 + ("AudioStream %p lost %d frames", this, underrunFrames)); 1.1005 + } 1.1006 + mLostFrames += underrunFrames; 1.1007 + servicedFrames += underrunFrames; 1.1008 + } 1.1009 + 1.1010 + WriteDumpFile(mDumpFile, this, aFrames, aBuffer); 1.1011 + // Don't log if we're not interested or if the stream is inactive 1.1012 + if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG) && 1.1013 + mState != SHUTDOWN && 1.1014 + insertTime != INT64_MAX && servicedFrames > underrunFrames) { 1.1015 + uint32_t latency = UINT32_MAX; 1.1016 + if (cubeb_stream_get_latency(mCubebStream, &latency)) { 1.1017 + NS_WARNING("Could not get latency from cubeb."); 1.1018 + } 1.1019 + TimeStamp now = TimeStamp::Now(); 1.1020 + 1.1021 + mLatencyLog->Log(AsyncLatencyLogger::AudioStream, reinterpret_cast<uint64_t>(this), 1.1022 + insertTime, now); 1.1023 + mLatencyLog->Log(AsyncLatencyLogger::Cubeb, reinterpret_cast<uint64_t>(mCubebStream.get()), 1.1024 + (latency * 1000) / mOutRate, now); 1.1025 + } 1.1026 + 1.1027 + mAudioClock.UpdateWritePosition(servicedFrames); 1.1028 + return servicedFrames; 1.1029 +} 1.1030 + 1.1031 +void 1.1032 +AudioStream::StateCallback(cubeb_state aState) 1.1033 +{ 1.1034 + MonitorAutoLock mon(mMonitor); 1.1035 + if (aState == CUBEB_STATE_DRAINED) { 1.1036 + mState = DRAINED; 1.1037 + } else if (aState == CUBEB_STATE_ERROR) { 1.1038 + LOG(("AudioStream::StateCallback() state %d cubeb error", mState)); 1.1039 + mState = ERRORED; 1.1040 + } 1.1041 + mon.NotifyAll(); 1.1042 +} 1.1043 + 1.1044 +AudioClock::AudioClock(AudioStream* aStream) 1.1045 + :mAudioStream(aStream), 1.1046 + mOldOutRate(0), 1.1047 + mBasePosition(0), 1.1048 + mBaseOffset(0), 1.1049 + mOldBaseOffset(0), 1.1050 + mOldBasePosition(0), 1.1051 + mPlaybackRateChangeOffset(0), 1.1052 + mPreviousPosition(0), 1.1053 + mWritten(0), 1.1054 + mOutRate(0), 1.1055 + mInRate(0), 1.1056 + mPreservesPitch(true), 1.1057 + mCompensatingLatency(false) 1.1058 +{} 1.1059 + 1.1060 +void AudioClock::Init() 1.1061 +{ 1.1062 + mOutRate = mAudioStream->GetRate(); 1.1063 + mInRate = mAudioStream->GetRate(); 1.1064 + mOldOutRate = mOutRate; 1.1065 +} 1.1066 + 1.1067 +void AudioClock::UpdateWritePosition(uint32_t aCount) 1.1068 +{ 1.1069 + mWritten += aCount; 1.1070 +} 1.1071 + 1.1072 +uint64_t AudioClock::GetPositionUnlocked() 1.1073 +{ 1.1074 + // GetPositionInFramesUnlocked() asserts it owns the monitor 1.1075 + int64_t position = mAudioStream->GetPositionInFramesUnlocked(); 1.1076 + int64_t diffOffset; 1.1077 + NS_ASSERTION(position < 0 || (mInRate != 0 && mOutRate != 0), "AudioClock not initialized."); 1.1078 + if (position >= 0) { 1.1079 + if (position < mPlaybackRateChangeOffset) { 1.1080 + // See if we are still playing frames pushed with the old playback rate in 1.1081 + // the backend. If we are, use the old output rate to compute the 1.1082 + // position. 1.1083 + mCompensatingLatency = true; 1.1084 + diffOffset = position - mOldBaseOffset; 1.1085 + position = static_cast<uint64_t>(mOldBasePosition + 1.1086 + static_cast<float>(USECS_PER_S * diffOffset) / mOldOutRate); 1.1087 + mPreviousPosition = position; 1.1088 + return position; 1.1089 + } 1.1090 + 1.1091 + if (mCompensatingLatency) { 1.1092 + diffOffset = position - mPlaybackRateChangeOffset; 1.1093 + mCompensatingLatency = false; 1.1094 + mBasePosition = mPreviousPosition; 1.1095 + } else { 1.1096 + diffOffset = position - mPlaybackRateChangeOffset; 1.1097 + } 1.1098 + position = static_cast<uint64_t>(mBasePosition + 1.1099 + (static_cast<float>(USECS_PER_S * diffOffset) / mOutRate)); 1.1100 + return position; 1.1101 + } 1.1102 + return UINT64_MAX; 1.1103 +} 1.1104 + 1.1105 +uint64_t AudioClock::GetPositionInFrames() 1.1106 +{ 1.1107 + return (GetPositionUnlocked() * mOutRate) / USECS_PER_S; 1.1108 +} 1.1109 + 1.1110 +void AudioClock::SetPlaybackRateUnlocked(double aPlaybackRate) 1.1111 +{ 1.1112 + // GetPositionInFramesUnlocked() asserts it owns the monitor 1.1113 + int64_t position = mAudioStream->GetPositionInFramesUnlocked(); 1.1114 + if (position > mPlaybackRateChangeOffset) { 1.1115 + mOldBasePosition = mBasePosition; 1.1116 + mBasePosition = GetPositionUnlocked(); 1.1117 + mOldBaseOffset = mPlaybackRateChangeOffset; 1.1118 + mBaseOffset = position; 1.1119 + mPlaybackRateChangeOffset = mWritten; 1.1120 + mOldOutRate = mOutRate; 1.1121 + mOutRate = static_cast<int>(mInRate / aPlaybackRate); 1.1122 + } else { 1.1123 + // The playbackRate has been changed before the end of the latency 1.1124 + // compensation phase. We don't update the mOld* variable. That way, the 1.1125 + // last playbackRate set is taken into account. 1.1126 + mBasePosition = GetPositionUnlocked(); 1.1127 + mBaseOffset = position; 1.1128 + mPlaybackRateChangeOffset = mWritten; 1.1129 + mOutRate = static_cast<int>(mInRate / aPlaybackRate); 1.1130 + } 1.1131 +} 1.1132 + 1.1133 +double AudioClock::GetPlaybackRate() 1.1134 +{ 1.1135 + return static_cast<double>(mInRate) / mOutRate; 1.1136 +} 1.1137 + 1.1138 +void AudioClock::SetPreservesPitch(bool aPreservesPitch) 1.1139 +{ 1.1140 + mPreservesPitch = aPreservesPitch; 1.1141 +} 1.1142 + 1.1143 +bool AudioClock::GetPreservesPitch() 1.1144 +{ 1.1145 + return mPreservesPitch; 1.1146 +} 1.1147 +} // namespace mozilla