Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | #include <stdio.h> |
michael@0 | 7 | #include <math.h> |
michael@0 | 8 | #include "prlog.h" |
michael@0 | 9 | #include "prdtoa.h" |
michael@0 | 10 | #include "AudioStream.h" |
michael@0 | 11 | #include "VideoUtils.h" |
michael@0 | 12 | #include "mozilla/Monitor.h" |
michael@0 | 13 | #include "mozilla/Mutex.h" |
michael@0 | 14 | #include <algorithm> |
michael@0 | 15 | #include "mozilla/Preferences.h" |
michael@0 | 16 | #include "soundtouch/SoundTouch.h" |
michael@0 | 17 | #include "Latency.h" |
michael@0 | 18 | |
michael@0 | 19 | namespace mozilla { |
michael@0 | 20 | |
michael@0 | 21 | #ifdef LOG |
michael@0 | 22 | #undef LOG |
michael@0 | 23 | #endif |
michael@0 | 24 | |
michael@0 | 25 | #ifdef PR_LOGGING |
michael@0 | 26 | PRLogModuleInfo* gAudioStreamLog = nullptr; |
michael@0 | 27 | // For simple logs |
michael@0 | 28 | #define LOG(x) PR_LOG(gAudioStreamLog, PR_LOG_DEBUG, x) |
michael@0 | 29 | #else |
michael@0 | 30 | #define LOG(x) |
michael@0 | 31 | #endif |
michael@0 | 32 | |
michael@0 | 33 | /** |
michael@0 | 34 | * When MOZ_DUMP_AUDIO is set in the environment (to anything), |
michael@0 | 35 | * we'll drop a series of files in the current working directory named |
michael@0 | 36 | * dumped-audio-<nnn>.wav, one per AudioStream created, containing |
michael@0 | 37 | * the audio for the stream including any skips due to underruns. |
michael@0 | 38 | */ |
michael@0 | 39 | static int gDumpedAudioCount = 0; |
michael@0 | 40 | |
michael@0 | 41 | #define PREF_VOLUME_SCALE "media.volume_scale" |
michael@0 | 42 | #define PREF_CUBEB_LATENCY "media.cubeb_latency_ms" |
michael@0 | 43 | |
michael@0 | 44 | static const uint32_t CUBEB_NORMAL_LATENCY_MS = 100; |
michael@0 | 45 | |
michael@0 | 46 | StaticMutex AudioStream::sMutex; |
michael@0 | 47 | cubeb* AudioStream::sCubebContext; |
michael@0 | 48 | uint32_t AudioStream::sPreferredSampleRate; |
michael@0 | 49 | double AudioStream::sVolumeScale; |
michael@0 | 50 | uint32_t AudioStream::sCubebLatency; |
michael@0 | 51 | bool AudioStream::sCubebLatencyPrefSet; |
michael@0 | 52 | |
michael@0 | 53 | /*static*/ void AudioStream::PrefChanged(const char* aPref, void* aClosure) |
michael@0 | 54 | { |
michael@0 | 55 | if (strcmp(aPref, PREF_VOLUME_SCALE) == 0) { |
michael@0 | 56 | nsAdoptingString value = Preferences::GetString(aPref); |
michael@0 | 57 | StaticMutexAutoLock lock(sMutex); |
michael@0 | 58 | if (value.IsEmpty()) { |
michael@0 | 59 | sVolumeScale = 1.0; |
michael@0 | 60 | } else { |
michael@0 | 61 | NS_ConvertUTF16toUTF8 utf8(value); |
michael@0 | 62 | sVolumeScale = std::max<double>(0, PR_strtod(utf8.get(), nullptr)); |
michael@0 | 63 | } |
michael@0 | 64 | } else if (strcmp(aPref, PREF_CUBEB_LATENCY) == 0) { |
michael@0 | 65 | // Arbitrary default stream latency of 100ms. The higher this |
michael@0 | 66 | // value, the longer stream volume changes will take to become |
michael@0 | 67 | // audible. |
michael@0 | 68 | sCubebLatencyPrefSet = Preferences::HasUserValue(aPref); |
michael@0 | 69 | uint32_t value = Preferences::GetUint(aPref, CUBEB_NORMAL_LATENCY_MS); |
michael@0 | 70 | StaticMutexAutoLock lock(sMutex); |
michael@0 | 71 | sCubebLatency = std::min<uint32_t>(std::max<uint32_t>(value, 1), 1000); |
michael@0 | 72 | } |
michael@0 | 73 | } |
michael@0 | 74 | |
michael@0 | 75 | /*static*/ double AudioStream::GetVolumeScale() |
michael@0 | 76 | { |
michael@0 | 77 | StaticMutexAutoLock lock(sMutex); |
michael@0 | 78 | return sVolumeScale; |
michael@0 | 79 | } |
michael@0 | 80 | |
michael@0 | 81 | /*static*/ cubeb* AudioStream::GetCubebContext() |
michael@0 | 82 | { |
michael@0 | 83 | StaticMutexAutoLock lock(sMutex); |
michael@0 | 84 | return GetCubebContextUnlocked(); |
michael@0 | 85 | } |
michael@0 | 86 | |
michael@0 | 87 | /*static*/ void AudioStream::InitPreferredSampleRate() |
michael@0 | 88 | { |
michael@0 | 89 | StaticMutexAutoLock lock(sMutex); |
michael@0 | 90 | if (sPreferredSampleRate == 0 && |
michael@0 | 91 | cubeb_get_preferred_sample_rate(GetCubebContextUnlocked(), |
michael@0 | 92 | &sPreferredSampleRate) != CUBEB_OK) { |
michael@0 | 93 | sPreferredSampleRate = 44100; |
michael@0 | 94 | } |
michael@0 | 95 | } |
michael@0 | 96 | |
michael@0 | 97 | /*static*/ cubeb* AudioStream::GetCubebContextUnlocked() |
michael@0 | 98 | { |
michael@0 | 99 | sMutex.AssertCurrentThreadOwns(); |
michael@0 | 100 | if (sCubebContext || |
michael@0 | 101 | cubeb_init(&sCubebContext, "AudioStream") == CUBEB_OK) { |
michael@0 | 102 | return sCubebContext; |
michael@0 | 103 | } |
michael@0 | 104 | NS_WARNING("cubeb_init failed"); |
michael@0 | 105 | return nullptr; |
michael@0 | 106 | } |
michael@0 | 107 | |
michael@0 | 108 | /*static*/ uint32_t AudioStream::GetCubebLatency() |
michael@0 | 109 | { |
michael@0 | 110 | StaticMutexAutoLock lock(sMutex); |
michael@0 | 111 | return sCubebLatency; |
michael@0 | 112 | } |
michael@0 | 113 | |
michael@0 | 114 | /*static*/ bool AudioStream::CubebLatencyPrefSet() |
michael@0 | 115 | { |
michael@0 | 116 | StaticMutexAutoLock lock(sMutex); |
michael@0 | 117 | return sCubebLatencyPrefSet; |
michael@0 | 118 | } |
michael@0 | 119 | |
michael@0 | 120 | #if defined(__ANDROID__) && defined(MOZ_B2G) |
michael@0 | 121 | static cubeb_stream_type ConvertChannelToCubebType(dom::AudioChannel aChannel) |
michael@0 | 122 | { |
michael@0 | 123 | switch(aChannel) { |
michael@0 | 124 | case dom::AudioChannel::Normal: |
michael@0 | 125 | return CUBEB_STREAM_TYPE_SYSTEM; |
michael@0 | 126 | case dom::AudioChannel::Content: |
michael@0 | 127 | return CUBEB_STREAM_TYPE_MUSIC; |
michael@0 | 128 | case dom::AudioChannel::Notification: |
michael@0 | 129 | return CUBEB_STREAM_TYPE_NOTIFICATION; |
michael@0 | 130 | case dom::AudioChannel::Alarm: |
michael@0 | 131 | return CUBEB_STREAM_TYPE_ALARM; |
michael@0 | 132 | case dom::AudioChannel::Telephony: |
michael@0 | 133 | return CUBEB_STREAM_TYPE_VOICE_CALL; |
michael@0 | 134 | case dom::AudioChannel::Ringer: |
michael@0 | 135 | return CUBEB_STREAM_TYPE_RING; |
michael@0 | 136 | // Currently Android openSLES library doesn't support FORCE_AUDIBLE yet. |
michael@0 | 137 | case dom::AudioChannel::Publicnotification: |
michael@0 | 138 | default: |
michael@0 | 139 | NS_ERROR("The value of AudioChannel is invalid"); |
michael@0 | 140 | return CUBEB_STREAM_TYPE_MAX; |
michael@0 | 141 | } |
michael@0 | 142 | } |
michael@0 | 143 | #endif |
michael@0 | 144 | |
michael@0 | 145 | AudioStream::AudioStream() |
michael@0 | 146 | : mMonitor("AudioStream") |
michael@0 | 147 | , mInRate(0) |
michael@0 | 148 | , mOutRate(0) |
michael@0 | 149 | , mChannels(0) |
michael@0 | 150 | , mOutChannels(0) |
michael@0 | 151 | , mWritten(0) |
michael@0 | 152 | , mAudioClock(MOZ_THIS_IN_INITIALIZER_LIST()) |
michael@0 | 153 | , mLatencyRequest(HighLatency) |
michael@0 | 154 | , mReadPoint(0) |
michael@0 | 155 | , mLostFrames(0) |
michael@0 | 156 | , mDumpFile(nullptr) |
michael@0 | 157 | , mVolume(1.0) |
michael@0 | 158 | , mBytesPerFrame(0) |
michael@0 | 159 | , mState(INITIALIZED) |
michael@0 | 160 | , mNeedsStart(false) |
michael@0 | 161 | { |
michael@0 | 162 | // keep a ref in case we shut down later than nsLayoutStatics |
michael@0 | 163 | mLatencyLog = AsyncLatencyLogger::Get(true); |
michael@0 | 164 | } |
michael@0 | 165 | |
michael@0 | 166 | AudioStream::~AudioStream() |
michael@0 | 167 | { |
michael@0 | 168 | LOG(("AudioStream: delete %p, state %d", this, mState)); |
michael@0 | 169 | Shutdown(); |
michael@0 | 170 | if (mDumpFile) { |
michael@0 | 171 | fclose(mDumpFile); |
michael@0 | 172 | } |
michael@0 | 173 | } |
michael@0 | 174 | |
michael@0 | 175 | size_t |
michael@0 | 176 | AudioStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const |
michael@0 | 177 | { |
michael@0 | 178 | size_t amount = aMallocSizeOf(this); |
michael@0 | 179 | |
michael@0 | 180 | // Possibly add in the future: |
michael@0 | 181 | // - mTimeStretcher |
michael@0 | 182 | // - mLatencyLog |
michael@0 | 183 | // - mCubebStream |
michael@0 | 184 | |
michael@0 | 185 | amount += mInserts.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 186 | amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 187 | |
michael@0 | 188 | return amount; |
michael@0 | 189 | } |
michael@0 | 190 | |
michael@0 | 191 | /*static*/ void AudioStream::InitLibrary() |
michael@0 | 192 | { |
michael@0 | 193 | #ifdef PR_LOGGING |
michael@0 | 194 | gAudioStreamLog = PR_NewLogModule("AudioStream"); |
michael@0 | 195 | #endif |
michael@0 | 196 | PrefChanged(PREF_VOLUME_SCALE, nullptr); |
michael@0 | 197 | Preferences::RegisterCallback(PrefChanged, PREF_VOLUME_SCALE); |
michael@0 | 198 | PrefChanged(PREF_CUBEB_LATENCY, nullptr); |
michael@0 | 199 | Preferences::RegisterCallback(PrefChanged, PREF_CUBEB_LATENCY); |
michael@0 | 200 | } |
michael@0 | 201 | |
michael@0 | 202 | /*static*/ void AudioStream::ShutdownLibrary() |
michael@0 | 203 | { |
michael@0 | 204 | Preferences::UnregisterCallback(PrefChanged, PREF_VOLUME_SCALE); |
michael@0 | 205 | Preferences::UnregisterCallback(PrefChanged, PREF_CUBEB_LATENCY); |
michael@0 | 206 | |
michael@0 | 207 | StaticMutexAutoLock lock(sMutex); |
michael@0 | 208 | if (sCubebContext) { |
michael@0 | 209 | cubeb_destroy(sCubebContext); |
michael@0 | 210 | sCubebContext = nullptr; |
michael@0 | 211 | } |
michael@0 | 212 | } |
michael@0 | 213 | |
michael@0 | 214 | nsresult AudioStream::EnsureTimeStretcherInitializedUnlocked() |
michael@0 | 215 | { |
michael@0 | 216 | mMonitor.AssertCurrentThreadOwns(); |
michael@0 | 217 | if (!mTimeStretcher) { |
michael@0 | 218 | mTimeStretcher = new soundtouch::SoundTouch(); |
michael@0 | 219 | mTimeStretcher->setSampleRate(mInRate); |
michael@0 | 220 | mTimeStretcher->setChannels(mOutChannels); |
michael@0 | 221 | mTimeStretcher->setPitch(1.0); |
michael@0 | 222 | } |
michael@0 | 223 | return NS_OK; |
michael@0 | 224 | } |
michael@0 | 225 | |
michael@0 | 226 | nsresult AudioStream::SetPlaybackRate(double aPlaybackRate) |
michael@0 | 227 | { |
michael@0 | 228 | NS_ASSERTION(aPlaybackRate > 0.0, |
michael@0 | 229 | "Can't handle negative or null playbackrate in the AudioStream."); |
michael@0 | 230 | // Avoid instantiating the resampler if we are not changing the playback rate. |
michael@0 | 231 | // GetPreservesPitch/SetPreservesPitch don't need locking before calling |
michael@0 | 232 | if (aPlaybackRate == mAudioClock.GetPlaybackRate()) { |
michael@0 | 233 | return NS_OK; |
michael@0 | 234 | } |
michael@0 | 235 | |
michael@0 | 236 | // MUST lock since the rate transposer is used from the cubeb callback, |
michael@0 | 237 | // and rate changes can cause the buffer to be reallocated |
michael@0 | 238 | MonitorAutoLock mon(mMonitor); |
michael@0 | 239 | if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) { |
michael@0 | 240 | return NS_ERROR_FAILURE; |
michael@0 | 241 | } |
michael@0 | 242 | |
michael@0 | 243 | mAudioClock.SetPlaybackRateUnlocked(aPlaybackRate); |
michael@0 | 244 | mOutRate = mInRate / aPlaybackRate; |
michael@0 | 245 | |
michael@0 | 246 | if (mAudioClock.GetPreservesPitch()) { |
michael@0 | 247 | mTimeStretcher->setTempo(aPlaybackRate); |
michael@0 | 248 | mTimeStretcher->setRate(1.0f); |
michael@0 | 249 | } else { |
michael@0 | 250 | mTimeStretcher->setTempo(1.0f); |
michael@0 | 251 | mTimeStretcher->setRate(aPlaybackRate); |
michael@0 | 252 | } |
michael@0 | 253 | return NS_OK; |
michael@0 | 254 | } |
michael@0 | 255 | |
michael@0 | 256 | nsresult AudioStream::SetPreservesPitch(bool aPreservesPitch) |
michael@0 | 257 | { |
michael@0 | 258 | // Avoid instantiating the timestretcher instance if not needed. |
michael@0 | 259 | if (aPreservesPitch == mAudioClock.GetPreservesPitch()) { |
michael@0 | 260 | return NS_OK; |
michael@0 | 261 | } |
michael@0 | 262 | |
michael@0 | 263 | // MUST lock since the rate transposer is used from the cubeb callback, |
michael@0 | 264 | // and rate changes can cause the buffer to be reallocated |
michael@0 | 265 | MonitorAutoLock mon(mMonitor); |
michael@0 | 266 | if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) { |
michael@0 | 267 | return NS_ERROR_FAILURE; |
michael@0 | 268 | } |
michael@0 | 269 | |
michael@0 | 270 | if (aPreservesPitch == true) { |
michael@0 | 271 | mTimeStretcher->setTempo(mAudioClock.GetPlaybackRate()); |
michael@0 | 272 | mTimeStretcher->setRate(1.0f); |
michael@0 | 273 | } else { |
michael@0 | 274 | mTimeStretcher->setTempo(1.0f); |
michael@0 | 275 | mTimeStretcher->setRate(mAudioClock.GetPlaybackRate()); |
michael@0 | 276 | } |
michael@0 | 277 | |
michael@0 | 278 | mAudioClock.SetPreservesPitch(aPreservesPitch); |
michael@0 | 279 | |
michael@0 | 280 | return NS_OK; |
michael@0 | 281 | } |
michael@0 | 282 | |
michael@0 | 283 | int64_t AudioStream::GetWritten() |
michael@0 | 284 | { |
michael@0 | 285 | return mWritten; |
michael@0 | 286 | } |
michael@0 | 287 | |
michael@0 | 288 | /*static*/ int AudioStream::MaxNumberOfChannels() |
michael@0 | 289 | { |
michael@0 | 290 | cubeb* cubebContext = GetCubebContext(); |
michael@0 | 291 | uint32_t maxNumberOfChannels; |
michael@0 | 292 | if (cubebContext && |
michael@0 | 293 | cubeb_get_max_channel_count(cubebContext, |
michael@0 | 294 | &maxNumberOfChannels) == CUBEB_OK) { |
michael@0 | 295 | return static_cast<int>(maxNumberOfChannels); |
michael@0 | 296 | } |
michael@0 | 297 | |
michael@0 | 298 | return 0; |
michael@0 | 299 | } |
michael@0 | 300 | |
michael@0 | 301 | /*static*/ int AudioStream::PreferredSampleRate() |
michael@0 | 302 | { |
michael@0 | 303 | MOZ_ASSERT(sPreferredSampleRate, |
michael@0 | 304 | "sPreferredSampleRate has not been initialized!"); |
michael@0 | 305 | return sPreferredSampleRate; |
michael@0 | 306 | } |
michael@0 | 307 | |
michael@0 | 308 | static void SetUint16LE(uint8_t* aDest, uint16_t aValue) |
michael@0 | 309 | { |
michael@0 | 310 | aDest[0] = aValue & 0xFF; |
michael@0 | 311 | aDest[1] = aValue >> 8; |
michael@0 | 312 | } |
michael@0 | 313 | |
michael@0 | 314 | static void SetUint32LE(uint8_t* aDest, uint32_t aValue) |
michael@0 | 315 | { |
michael@0 | 316 | SetUint16LE(aDest, aValue & 0xFFFF); |
michael@0 | 317 | SetUint16LE(aDest + 2, aValue >> 16); |
michael@0 | 318 | } |
michael@0 | 319 | |
michael@0 | 320 | static FILE* |
michael@0 | 321 | OpenDumpFile(AudioStream* aStream) |
michael@0 | 322 | { |
michael@0 | 323 | if (!getenv("MOZ_DUMP_AUDIO")) |
michael@0 | 324 | return nullptr; |
michael@0 | 325 | char buf[100]; |
michael@0 | 326 | sprintf(buf, "dumped-audio-%d.wav", gDumpedAudioCount); |
michael@0 | 327 | FILE* f = fopen(buf, "wb"); |
michael@0 | 328 | if (!f) |
michael@0 | 329 | return nullptr; |
michael@0 | 330 | ++gDumpedAudioCount; |
michael@0 | 331 | |
michael@0 | 332 | uint8_t header[] = { |
michael@0 | 333 | // RIFF header |
michael@0 | 334 | 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, |
michael@0 | 335 | // fmt chunk. We always write 16-bit samples. |
michael@0 | 336 | 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFF, 0xFF, |
michael@0 | 337 | 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x10, 0x00, |
michael@0 | 338 | // data chunk |
michael@0 | 339 | 0x64, 0x61, 0x74, 0x61, 0xFE, 0xFF, 0xFF, 0x7F |
michael@0 | 340 | }; |
michael@0 | 341 | static const int CHANNEL_OFFSET = 22; |
michael@0 | 342 | static const int SAMPLE_RATE_OFFSET = 24; |
michael@0 | 343 | static const int BLOCK_ALIGN_OFFSET = 32; |
michael@0 | 344 | SetUint16LE(header + CHANNEL_OFFSET, aStream->GetChannels()); |
michael@0 | 345 | SetUint32LE(header + SAMPLE_RATE_OFFSET, aStream->GetRate()); |
michael@0 | 346 | SetUint16LE(header + BLOCK_ALIGN_OFFSET, aStream->GetChannels()*2); |
michael@0 | 347 | fwrite(header, sizeof(header), 1, f); |
michael@0 | 348 | |
michael@0 | 349 | return f; |
michael@0 | 350 | } |
michael@0 | 351 | |
michael@0 | 352 | static void |
michael@0 | 353 | WriteDumpFile(FILE* aDumpFile, AudioStream* aStream, uint32_t aFrames, |
michael@0 | 354 | void* aBuffer) |
michael@0 | 355 | { |
michael@0 | 356 | if (!aDumpFile) |
michael@0 | 357 | return; |
michael@0 | 358 | |
michael@0 | 359 | uint32_t samples = aStream->GetOutChannels()*aFrames; |
michael@0 | 360 | if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) { |
michael@0 | 361 | fwrite(aBuffer, 2, samples, aDumpFile); |
michael@0 | 362 | return; |
michael@0 | 363 | } |
michael@0 | 364 | |
michael@0 | 365 | NS_ASSERTION(AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_FLOAT32, "bad format"); |
michael@0 | 366 | nsAutoTArray<uint8_t, 1024*2> buf; |
michael@0 | 367 | buf.SetLength(samples*2); |
michael@0 | 368 | float* input = static_cast<float*>(aBuffer); |
michael@0 | 369 | uint8_t* output = buf.Elements(); |
michael@0 | 370 | for (uint32_t i = 0; i < samples; ++i) { |
michael@0 | 371 | SetUint16LE(output + i*2, int16_t(input[i]*32767.0f)); |
michael@0 | 372 | } |
michael@0 | 373 | fwrite(output, 2, samples, aDumpFile); |
michael@0 | 374 | fflush(aDumpFile); |
michael@0 | 375 | } |
michael@0 | 376 | |
michael@0 | 377 | // NOTE: this must not block a LowLatency stream for any significant amount |
michael@0 | 378 | // of time, or it will block the entirety of MSG |
michael@0 | 379 | nsresult |
michael@0 | 380 | AudioStream::Init(int32_t aNumChannels, int32_t aRate, |
michael@0 | 381 | const dom::AudioChannel aAudioChannel, |
michael@0 | 382 | LatencyRequest aLatencyRequest) |
michael@0 | 383 | { |
michael@0 | 384 | if (!GetCubebContext() || aNumChannels < 0 || aRate < 0) { |
michael@0 | 385 | return NS_ERROR_FAILURE; |
michael@0 | 386 | } |
michael@0 | 387 | |
michael@0 | 388 | PR_LOG(gAudioStreamLog, PR_LOG_DEBUG, |
michael@0 | 389 | ("%s channels: %d, rate: %d for %p", __FUNCTION__, aNumChannels, aRate, this)); |
michael@0 | 390 | mInRate = mOutRate = aRate; |
michael@0 | 391 | mChannels = aNumChannels; |
michael@0 | 392 | mOutChannels = (aNumChannels > 2) ? 2 : aNumChannels; |
michael@0 | 393 | mLatencyRequest = aLatencyRequest; |
michael@0 | 394 | |
michael@0 | 395 | mDumpFile = OpenDumpFile(this); |
michael@0 | 396 | |
michael@0 | 397 | cubeb_stream_params params; |
michael@0 | 398 | params.rate = aRate; |
michael@0 | 399 | params.channels = mOutChannels; |
michael@0 | 400 | #if defined(__ANDROID__) |
michael@0 | 401 | #if defined(MOZ_B2G) |
michael@0 | 402 | params.stream_type = ConvertChannelToCubebType(aAudioChannel); |
michael@0 | 403 | #else |
michael@0 | 404 | params.stream_type = CUBEB_STREAM_TYPE_MUSIC; |
michael@0 | 405 | #endif |
michael@0 | 406 | |
michael@0 | 407 | if (params.stream_type == CUBEB_STREAM_TYPE_MAX) { |
michael@0 | 408 | return NS_ERROR_INVALID_ARG; |
michael@0 | 409 | } |
michael@0 | 410 | #endif |
michael@0 | 411 | if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) { |
michael@0 | 412 | params.format = CUBEB_SAMPLE_S16NE; |
michael@0 | 413 | } else { |
michael@0 | 414 | params.format = CUBEB_SAMPLE_FLOAT32NE; |
michael@0 | 415 | } |
michael@0 | 416 | mBytesPerFrame = sizeof(AudioDataValue) * mOutChannels; |
michael@0 | 417 | |
michael@0 | 418 | mAudioClock.Init(); |
michael@0 | 419 | |
michael@0 | 420 | // Size mBuffer for one second of audio. This value is arbitrary, and was |
michael@0 | 421 | // selected based on the observed behaviour of the existing AudioStream |
michael@0 | 422 | // implementations. |
michael@0 | 423 | uint32_t bufferLimit = FramesToBytes(aRate); |
michael@0 | 424 | NS_ABORT_IF_FALSE(bufferLimit % mBytesPerFrame == 0, "Must buffer complete frames"); |
michael@0 | 425 | mBuffer.SetCapacity(bufferLimit); |
michael@0 | 426 | |
michael@0 | 427 | if (aLatencyRequest == LowLatency) { |
michael@0 | 428 | // Don't block this thread to initialize a cubeb stream. |
michael@0 | 429 | // When this is done, it will start callbacks from Cubeb. Those will |
michael@0 | 430 | // cause us to move from INITIALIZED to RUNNING. Until then, we |
michael@0 | 431 | // can't access any cubeb functions. |
michael@0 | 432 | // Use a RefPtr to avoid leaks if Dispatch fails |
michael@0 | 433 | RefPtr<AudioInitTask> init = new AudioInitTask(this, aLatencyRequest, params); |
michael@0 | 434 | init->Dispatch(); |
michael@0 | 435 | return NS_OK; |
michael@0 | 436 | } |
michael@0 | 437 | // High latency - open synchronously |
michael@0 | 438 | nsresult rv = OpenCubeb(params, aLatencyRequest); |
michael@0 | 439 | // See if we need to start() the stream, since we must do that from this |
michael@0 | 440 | // thread for now (cubeb API issue) |
michael@0 | 441 | CheckForStart(); |
michael@0 | 442 | return rv; |
michael@0 | 443 | } |
michael@0 | 444 | |
michael@0 | 445 | // This code used to live inside AudioStream::Init(), but on Mac (others?) |
michael@0 | 446 | // it has been known to take 300-800 (or even 8500) ms to execute(!) |
michael@0 | 447 | nsresult |
michael@0 | 448 | AudioStream::OpenCubeb(cubeb_stream_params &aParams, |
michael@0 | 449 | LatencyRequest aLatencyRequest) |
michael@0 | 450 | { |
michael@0 | 451 | cubeb* cubebContext = GetCubebContext(); |
michael@0 | 452 | if (!cubebContext) { |
michael@0 | 453 | MonitorAutoLock mon(mMonitor); |
michael@0 | 454 | mState = AudioStream::ERRORED; |
michael@0 | 455 | return NS_ERROR_FAILURE; |
michael@0 | 456 | } |
michael@0 | 457 | |
michael@0 | 458 | // If the latency pref is set, use it. Otherwise, if this stream is intended |
michael@0 | 459 | // for low latency playback, try to get the lowest latency possible. |
michael@0 | 460 | // Otherwise, for normal streams, use 100ms. |
michael@0 | 461 | uint32_t latency; |
michael@0 | 462 | if (aLatencyRequest == LowLatency && !CubebLatencyPrefSet()) { |
michael@0 | 463 | if (cubeb_get_min_latency(cubebContext, aParams, &latency) != CUBEB_OK) { |
michael@0 | 464 | latency = GetCubebLatency(); |
michael@0 | 465 | } |
michael@0 | 466 | } else { |
michael@0 | 467 | latency = GetCubebLatency(); |
michael@0 | 468 | } |
michael@0 | 469 | |
michael@0 | 470 | { |
michael@0 | 471 | cubeb_stream* stream; |
michael@0 | 472 | if (cubeb_stream_init(cubebContext, &stream, "AudioStream", aParams, |
michael@0 | 473 | latency, DataCallback_S, StateCallback_S, this) == CUBEB_OK) { |
michael@0 | 474 | MonitorAutoLock mon(mMonitor); |
michael@0 | 475 | mCubebStream.own(stream); |
michael@0 | 476 | // Make sure we weren't shut down while in flight! |
michael@0 | 477 | if (mState == SHUTDOWN) { |
michael@0 | 478 | mCubebStream.reset(); |
michael@0 | 479 | LOG(("AudioStream::OpenCubeb() %p Shutdown while opening cubeb", this)); |
michael@0 | 480 | return NS_ERROR_FAILURE; |
michael@0 | 481 | } |
michael@0 | 482 | |
michael@0 | 483 | // We can't cubeb_stream_start() the thread from a transient thread due to |
michael@0 | 484 | // cubeb API requirements (init can be called from another thread, but |
michael@0 | 485 | // not start/stop/destroy/etc) |
michael@0 | 486 | } else { |
michael@0 | 487 | MonitorAutoLock mon(mMonitor); |
michael@0 | 488 | mState = ERRORED; |
michael@0 | 489 | LOG(("AudioStream::OpenCubeb() %p failed to init cubeb", this)); |
michael@0 | 490 | return NS_ERROR_FAILURE; |
michael@0 | 491 | } |
michael@0 | 492 | } |
michael@0 | 493 | |
michael@0 | 494 | return NS_OK; |
michael@0 | 495 | } |
michael@0 | 496 | |
michael@0 | 497 | void |
michael@0 | 498 | AudioStream::CheckForStart() |
michael@0 | 499 | { |
michael@0 | 500 | if (mState == INITIALIZED) { |
michael@0 | 501 | // Start the stream right away when low latency has been requested. This means |
michael@0 | 502 | // that the DataCallback will feed silence to cubeb, until the first frames |
michael@0 | 503 | // are written to this AudioStream. Also start if a start has been queued. |
michael@0 | 504 | if (mLatencyRequest == LowLatency || mNeedsStart) { |
michael@0 | 505 | StartUnlocked(); // mState = STARTED or ERRORED |
michael@0 | 506 | mNeedsStart = false; |
michael@0 | 507 | PR_LOG(gAudioStreamLog, PR_LOG_WARNING, |
michael@0 | 508 | ("Started waiting %s-latency stream", |
michael@0 | 509 | mLatencyRequest == LowLatency ? "low" : "high")); |
michael@0 | 510 | } else { |
michael@0 | 511 | // high latency, not full - OR Pause() was called before we got here |
michael@0 | 512 | PR_LOG(gAudioStreamLog, PR_LOG_DEBUG, |
michael@0 | 513 | ("Not starting waiting %s-latency stream", |
michael@0 | 514 | mLatencyRequest == LowLatency ? "low" : "high")); |
michael@0 | 515 | } |
michael@0 | 516 | } |
michael@0 | 517 | } |
michael@0 | 518 | |
michael@0 | 519 | NS_IMETHODIMP |
michael@0 | 520 | AudioInitTask::Run() |
michael@0 | 521 | { |
michael@0 | 522 | MOZ_ASSERT(mThread); |
michael@0 | 523 | if (NS_IsMainThread()) { |
michael@0 | 524 | mThread->Shutdown(); // can't Shutdown from the thread itself, darn |
michael@0 | 525 | // Don't null out mThread! |
michael@0 | 526 | // See bug 999104. We must hold a ref to the thread across Dispatch() |
michael@0 | 527 | // since the internal mThread ref could be released while processing |
michael@0 | 528 | // the Dispatch(), and Dispatch/PutEvent itself doesn't hold a ref; it |
michael@0 | 529 | // assumes the caller does. |
michael@0 | 530 | return NS_OK; |
michael@0 | 531 | } |
michael@0 | 532 | |
michael@0 | 533 | nsresult rv = mAudioStream->OpenCubeb(mParams, mLatencyRequest); |
michael@0 | 534 | |
michael@0 | 535 | // and now kill this thread |
michael@0 | 536 | NS_DispatchToMainThread(this); |
michael@0 | 537 | return rv; |
michael@0 | 538 | } |
michael@0 | 539 | |
michael@0 | 540 | // aTime is the time in ms the samples were inserted into MediaStreamGraph |
michael@0 | 541 | nsresult |
michael@0 | 542 | AudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames, TimeStamp *aTime) |
michael@0 | 543 | { |
michael@0 | 544 | MonitorAutoLock mon(mMonitor); |
michael@0 | 545 | if (mState == ERRORED) { |
michael@0 | 546 | return NS_ERROR_FAILURE; |
michael@0 | 547 | } |
michael@0 | 548 | NS_ASSERTION(mState == INITIALIZED || mState == STARTED || mState == RUNNING, |
michael@0 | 549 | "Stream write in unexpected state."); |
michael@0 | 550 | |
michael@0 | 551 | // See if we need to start() the stream, since we must do that from this thread |
michael@0 | 552 | CheckForStart(); |
michael@0 | 553 | |
michael@0 | 554 | // Downmix to Stereo. |
michael@0 | 555 | if (mChannels > 2 && mChannels <= 8) { |
michael@0 | 556 | DownmixAudioToStereo(const_cast<AudioDataValue*> (aBuf), mChannels, aFrames); |
michael@0 | 557 | } |
michael@0 | 558 | else if (mChannels > 8) { |
michael@0 | 559 | return NS_ERROR_FAILURE; |
michael@0 | 560 | } |
michael@0 | 561 | |
michael@0 | 562 | const uint8_t* src = reinterpret_cast<const uint8_t*>(aBuf); |
michael@0 | 563 | uint32_t bytesToCopy = FramesToBytes(aFrames); |
michael@0 | 564 | |
michael@0 | 565 | // XXX this will need to change if we want to enable this on-the-fly! |
michael@0 | 566 | if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG)) { |
michael@0 | 567 | // Record the position and time this data was inserted |
michael@0 | 568 | int64_t timeMs; |
michael@0 | 569 | if (aTime && !aTime->IsNull()) { |
michael@0 | 570 | if (mStartTime.IsNull()) { |
michael@0 | 571 | AsyncLatencyLogger::Get(true)->GetStartTime(mStartTime); |
michael@0 | 572 | } |
michael@0 | 573 | timeMs = (*aTime - mStartTime).ToMilliseconds(); |
michael@0 | 574 | } else { |
michael@0 | 575 | timeMs = 0; |
michael@0 | 576 | } |
michael@0 | 577 | struct Inserts insert = { timeMs, aFrames}; |
michael@0 | 578 | mInserts.AppendElement(insert); |
michael@0 | 579 | } |
michael@0 | 580 | |
michael@0 | 581 | while (bytesToCopy > 0) { |
michael@0 | 582 | uint32_t available = std::min(bytesToCopy, mBuffer.Available()); |
michael@0 | 583 | NS_ABORT_IF_FALSE(available % mBytesPerFrame == 0, |
michael@0 | 584 | "Must copy complete frames."); |
michael@0 | 585 | |
michael@0 | 586 | mBuffer.AppendElements(src, available); |
michael@0 | 587 | src += available; |
michael@0 | 588 | bytesToCopy -= available; |
michael@0 | 589 | |
michael@0 | 590 | if (bytesToCopy > 0) { |
michael@0 | 591 | // Careful - the CubebInit thread may not have gotten to STARTED yet |
michael@0 | 592 | if ((mState == INITIALIZED || mState == STARTED) && mLatencyRequest == LowLatency) { |
michael@0 | 593 | // don't ever block MediaStreamGraph low-latency streams |
michael@0 | 594 | uint32_t remains = 0; // we presume the buffer is full |
michael@0 | 595 | if (mBuffer.Length() > bytesToCopy) { |
michael@0 | 596 | remains = mBuffer.Length() - bytesToCopy; // Free up just enough space |
michael@0 | 597 | } |
michael@0 | 598 | // account for dropping samples |
michael@0 | 599 | PR_LOG(gAudioStreamLog, PR_LOG_WARNING, ("Stream %p dropping %u bytes (%u frames)in Write()", |
michael@0 | 600 | this, mBuffer.Length() - remains, BytesToFrames(mBuffer.Length() - remains))); |
michael@0 | 601 | mReadPoint += BytesToFrames(mBuffer.Length() - remains); |
michael@0 | 602 | mBuffer.ContractTo(remains); |
michael@0 | 603 | } else { // RUNNING or high latency |
michael@0 | 604 | // If we are not playing, but our buffer is full, start playing to make |
michael@0 | 605 | // room for soon-to-be-decoded data. |
michael@0 | 606 | if (mState != STARTED && mState != RUNNING) { |
michael@0 | 607 | PR_LOG(gAudioStreamLog, PR_LOG_WARNING, ("Starting stream %p in Write (%u waiting)", |
michael@0 | 608 | this, bytesToCopy)); |
michael@0 | 609 | StartUnlocked(); |
michael@0 | 610 | if (mState == ERRORED) { |
michael@0 | 611 | return NS_ERROR_FAILURE; |
michael@0 | 612 | } |
michael@0 | 613 | } |
michael@0 | 614 | PR_LOG(gAudioStreamLog, PR_LOG_WARNING, ("Stream %p waiting in Write() (%u waiting)", |
michael@0 | 615 | this, bytesToCopy)); |
michael@0 | 616 | mon.Wait(); |
michael@0 | 617 | } |
michael@0 | 618 | } |
michael@0 | 619 | } |
michael@0 | 620 | |
michael@0 | 621 | mWritten += aFrames; |
michael@0 | 622 | return NS_OK; |
michael@0 | 623 | } |
michael@0 | 624 | |
michael@0 | 625 | uint32_t |
michael@0 | 626 | AudioStream::Available() |
michael@0 | 627 | { |
michael@0 | 628 | MonitorAutoLock mon(mMonitor); |
michael@0 | 629 | NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Buffer invariant violated."); |
michael@0 | 630 | return BytesToFrames(mBuffer.Available()); |
michael@0 | 631 | } |
michael@0 | 632 | |
michael@0 | 633 | void |
michael@0 | 634 | AudioStream::SetVolume(double aVolume) |
michael@0 | 635 | { |
michael@0 | 636 | MonitorAutoLock mon(mMonitor); |
michael@0 | 637 | NS_ABORT_IF_FALSE(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume"); |
michael@0 | 638 | mVolume = aVolume; |
michael@0 | 639 | } |
michael@0 | 640 | |
michael@0 | 641 | void |
michael@0 | 642 | AudioStream::Drain() |
michael@0 | 643 | { |
michael@0 | 644 | MonitorAutoLock mon(mMonitor); |
michael@0 | 645 | LOG(("AudioStream::Drain() for %p, state %d, avail %u", this, mState, mBuffer.Available())); |
michael@0 | 646 | if (mState != STARTED && mState != RUNNING) { |
michael@0 | 647 | NS_ASSERTION(mState == ERRORED || mBuffer.Available() == 0, "Draining without full buffer of unplayed audio"); |
michael@0 | 648 | return; |
michael@0 | 649 | } |
michael@0 | 650 | mState = DRAINING; |
michael@0 | 651 | while (mState == DRAINING) { |
michael@0 | 652 | mon.Wait(); |
michael@0 | 653 | } |
michael@0 | 654 | } |
michael@0 | 655 | |
michael@0 | 656 | void |
michael@0 | 657 | AudioStream::Start() |
michael@0 | 658 | { |
michael@0 | 659 | MonitorAutoLock mon(mMonitor); |
michael@0 | 660 | StartUnlocked(); |
michael@0 | 661 | } |
michael@0 | 662 | |
michael@0 | 663 | void |
michael@0 | 664 | AudioStream::StartUnlocked() |
michael@0 | 665 | { |
michael@0 | 666 | mMonitor.AssertCurrentThreadOwns(); |
michael@0 | 667 | if (!mCubebStream) { |
michael@0 | 668 | mNeedsStart = true; |
michael@0 | 669 | return; |
michael@0 | 670 | } |
michael@0 | 671 | MonitorAutoUnlock mon(mMonitor); |
michael@0 | 672 | if (mState == INITIALIZED) { |
michael@0 | 673 | int r = cubeb_stream_start(mCubebStream); |
michael@0 | 674 | mState = r == CUBEB_OK ? STARTED : ERRORED; |
michael@0 | 675 | LOG(("AudioStream: started %p, state %s", this, mState == STARTED ? "STARTED" : "ERRORED")); |
michael@0 | 676 | } |
michael@0 | 677 | } |
michael@0 | 678 | |
michael@0 | 679 | void |
michael@0 | 680 | AudioStream::Pause() |
michael@0 | 681 | { |
michael@0 | 682 | MonitorAutoLock mon(mMonitor); |
michael@0 | 683 | if (!mCubebStream || (mState != STARTED && mState != RUNNING)) { |
michael@0 | 684 | mNeedsStart = false; |
michael@0 | 685 | mState = STOPPED; // which also tells async OpenCubeb not to start, just init |
michael@0 | 686 | return; |
michael@0 | 687 | } |
michael@0 | 688 | |
michael@0 | 689 | int r; |
michael@0 | 690 | { |
michael@0 | 691 | MonitorAutoUnlock mon(mMonitor); |
michael@0 | 692 | r = cubeb_stream_stop(mCubebStream); |
michael@0 | 693 | } |
michael@0 | 694 | if (mState != ERRORED && r == CUBEB_OK) { |
michael@0 | 695 | mState = STOPPED; |
michael@0 | 696 | } |
michael@0 | 697 | } |
michael@0 | 698 | |
michael@0 | 699 | void |
michael@0 | 700 | AudioStream::Resume() |
michael@0 | 701 | { |
michael@0 | 702 | MonitorAutoLock mon(mMonitor); |
michael@0 | 703 | if (!mCubebStream || mState != STOPPED) { |
michael@0 | 704 | return; |
michael@0 | 705 | } |
michael@0 | 706 | |
michael@0 | 707 | int r; |
michael@0 | 708 | { |
michael@0 | 709 | MonitorAutoUnlock mon(mMonitor); |
michael@0 | 710 | r = cubeb_stream_start(mCubebStream); |
michael@0 | 711 | } |
michael@0 | 712 | if (mState != ERRORED && r == CUBEB_OK) { |
michael@0 | 713 | mState = STARTED; |
michael@0 | 714 | } |
michael@0 | 715 | } |
michael@0 | 716 | |
michael@0 | 717 | void |
michael@0 | 718 | AudioStream::Shutdown() |
michael@0 | 719 | { |
michael@0 | 720 | LOG(("AudioStream: Shutdown %p, state %d", this, mState)); |
michael@0 | 721 | { |
michael@0 | 722 | MonitorAutoLock mon(mMonitor); |
michael@0 | 723 | if (mState == STARTED || mState == RUNNING) { |
michael@0 | 724 | MonitorAutoUnlock mon(mMonitor); |
michael@0 | 725 | Pause(); |
michael@0 | 726 | } |
michael@0 | 727 | MOZ_ASSERT(mState != STARTED && mState != RUNNING); // paranoia |
michael@0 | 728 | mState = SHUTDOWN; |
michael@0 | 729 | } |
michael@0 | 730 | // Must not try to shut down cubeb from within the lock! wasapi may still |
michael@0 | 731 | // call our callback after Pause()/stop()!?! Bug 996162 |
michael@0 | 732 | if (mCubebStream) { |
michael@0 | 733 | mCubebStream.reset(); |
michael@0 | 734 | } |
michael@0 | 735 | } |
michael@0 | 736 | |
michael@0 | 737 | int64_t |
michael@0 | 738 | AudioStream::GetPosition() |
michael@0 | 739 | { |
michael@0 | 740 | MonitorAutoLock mon(mMonitor); |
michael@0 | 741 | return mAudioClock.GetPositionUnlocked(); |
michael@0 | 742 | } |
michael@0 | 743 | |
michael@0 | 744 | // This function is miscompiled by PGO with MSVC 2010. See bug 768333. |
michael@0 | 745 | #ifdef _MSC_VER |
michael@0 | 746 | #pragma optimize("", off) |
michael@0 | 747 | #endif |
michael@0 | 748 | int64_t |
michael@0 | 749 | AudioStream::GetPositionInFrames() |
michael@0 | 750 | { |
michael@0 | 751 | return mAudioClock.GetPositionInFrames(); |
michael@0 | 752 | } |
michael@0 | 753 | #ifdef _MSC_VER |
michael@0 | 754 | #pragma optimize("", on) |
michael@0 | 755 | #endif |
michael@0 | 756 | |
michael@0 | 757 | int64_t |
michael@0 | 758 | AudioStream::GetPositionInFramesInternal() |
michael@0 | 759 | { |
michael@0 | 760 | MonitorAutoLock mon(mMonitor); |
michael@0 | 761 | return GetPositionInFramesUnlocked(); |
michael@0 | 762 | } |
michael@0 | 763 | |
michael@0 | 764 | int64_t |
michael@0 | 765 | AudioStream::GetPositionInFramesUnlocked() |
michael@0 | 766 | { |
michael@0 | 767 | mMonitor.AssertCurrentThreadOwns(); |
michael@0 | 768 | |
michael@0 | 769 | if (!mCubebStream || mState == ERRORED) { |
michael@0 | 770 | return -1; |
michael@0 | 771 | } |
michael@0 | 772 | |
michael@0 | 773 | uint64_t position = 0; |
michael@0 | 774 | { |
michael@0 | 775 | MonitorAutoUnlock mon(mMonitor); |
michael@0 | 776 | if (cubeb_stream_get_position(mCubebStream, &position) != CUBEB_OK) { |
michael@0 | 777 | return -1; |
michael@0 | 778 | } |
michael@0 | 779 | } |
michael@0 | 780 | |
michael@0 | 781 | // Adjust the reported position by the number of silent frames written |
michael@0 | 782 | // during stream underruns. |
michael@0 | 783 | uint64_t adjustedPosition = 0; |
michael@0 | 784 | if (position >= mLostFrames) { |
michael@0 | 785 | adjustedPosition = position - mLostFrames; |
michael@0 | 786 | } |
michael@0 | 787 | return std::min<uint64_t>(adjustedPosition, INT64_MAX); |
michael@0 | 788 | } |
michael@0 | 789 | |
michael@0 | 790 | int64_t |
michael@0 | 791 | AudioStream::GetLatencyInFrames() |
michael@0 | 792 | { |
michael@0 | 793 | uint32_t latency; |
michael@0 | 794 | if (cubeb_stream_get_latency(mCubebStream, &latency)) { |
michael@0 | 795 | NS_WARNING("Could not get cubeb latency."); |
michael@0 | 796 | return 0; |
michael@0 | 797 | } |
michael@0 | 798 | return static_cast<int64_t>(latency); |
michael@0 | 799 | } |
michael@0 | 800 | |
michael@0 | 801 | bool |
michael@0 | 802 | AudioStream::IsPaused() |
michael@0 | 803 | { |
michael@0 | 804 | MonitorAutoLock mon(mMonitor); |
michael@0 | 805 | return mState == STOPPED; |
michael@0 | 806 | } |
michael@0 | 807 | |
michael@0 | 808 | void |
michael@0 | 809 | AudioStream::GetBufferInsertTime(int64_t &aTimeMs) |
michael@0 | 810 | { |
michael@0 | 811 | if (mInserts.Length() > 0) { |
michael@0 | 812 | // Find the right block, but don't leave the array empty |
michael@0 | 813 | while (mInserts.Length() > 1 && mReadPoint >= mInserts[0].mFrames) { |
michael@0 | 814 | mReadPoint -= mInserts[0].mFrames; |
michael@0 | 815 | mInserts.RemoveElementAt(0); |
michael@0 | 816 | } |
michael@0 | 817 | // offset for amount already read |
michael@0 | 818 | // XXX Note: could misreport if we couldn't find a block in the right timeframe |
michael@0 | 819 | aTimeMs = mInserts[0].mTimeMs + ((mReadPoint * 1000) / mOutRate); |
michael@0 | 820 | } else { |
michael@0 | 821 | aTimeMs = INT64_MAX; |
michael@0 | 822 | } |
michael@0 | 823 | } |
michael@0 | 824 | |
michael@0 | 825 | long |
michael@0 | 826 | AudioStream::GetUnprocessed(void* aBuffer, long aFrames, int64_t &aTimeMs) |
michael@0 | 827 | { |
michael@0 | 828 | uint8_t* wpos = reinterpret_cast<uint8_t*>(aBuffer); |
michael@0 | 829 | |
michael@0 | 830 | // Flush the timestretcher pipeline, if we were playing using a playback rate |
michael@0 | 831 | // other than 1.0. |
michael@0 | 832 | uint32_t flushedFrames = 0; |
michael@0 | 833 | if (mTimeStretcher && mTimeStretcher->numSamples()) { |
michael@0 | 834 | flushedFrames = mTimeStretcher->receiveSamples(reinterpret_cast<AudioDataValue*>(wpos), aFrames); |
michael@0 | 835 | wpos += FramesToBytes(flushedFrames); |
michael@0 | 836 | } |
michael@0 | 837 | uint32_t toPopBytes = FramesToBytes(aFrames - flushedFrames); |
michael@0 | 838 | uint32_t available = std::min(toPopBytes, mBuffer.Length()); |
michael@0 | 839 | |
michael@0 | 840 | void* input[2]; |
michael@0 | 841 | uint32_t input_size[2]; |
michael@0 | 842 | mBuffer.PopElements(available, &input[0], &input_size[0], &input[1], &input_size[1]); |
michael@0 | 843 | memcpy(wpos, input[0], input_size[0]); |
michael@0 | 844 | wpos += input_size[0]; |
michael@0 | 845 | memcpy(wpos, input[1], input_size[1]); |
michael@0 | 846 | |
michael@0 | 847 | // First time block now has our first returned sample |
michael@0 | 848 | mReadPoint += BytesToFrames(available); |
michael@0 | 849 | GetBufferInsertTime(aTimeMs); |
michael@0 | 850 | |
michael@0 | 851 | return BytesToFrames(available) + flushedFrames; |
michael@0 | 852 | } |
michael@0 | 853 | |
michael@0 | 854 | // Get unprocessed samples, and pad the beginning of the buffer with silence if |
michael@0 | 855 | // there is not enough data. |
michael@0 | 856 | long |
michael@0 | 857 | AudioStream::GetUnprocessedWithSilencePadding(void* aBuffer, long aFrames, int64_t& aTimeMs) |
michael@0 | 858 | { |
michael@0 | 859 | uint32_t toPopBytes = FramesToBytes(aFrames); |
michael@0 | 860 | uint32_t available = std::min(toPopBytes, mBuffer.Length()); |
michael@0 | 861 | uint32_t silenceOffset = toPopBytes - available; |
michael@0 | 862 | |
michael@0 | 863 | uint8_t* wpos = reinterpret_cast<uint8_t*>(aBuffer); |
michael@0 | 864 | |
michael@0 | 865 | memset(wpos, 0, silenceOffset); |
michael@0 | 866 | wpos += silenceOffset; |
michael@0 | 867 | |
michael@0 | 868 | void* input[2]; |
michael@0 | 869 | uint32_t input_size[2]; |
michael@0 | 870 | mBuffer.PopElements(available, &input[0], &input_size[0], &input[1], &input_size[1]); |
michael@0 | 871 | memcpy(wpos, input[0], input_size[0]); |
michael@0 | 872 | wpos += input_size[0]; |
michael@0 | 873 | memcpy(wpos, input[1], input_size[1]); |
michael@0 | 874 | |
michael@0 | 875 | GetBufferInsertTime(aTimeMs); |
michael@0 | 876 | |
michael@0 | 877 | return aFrames; |
michael@0 | 878 | } |
michael@0 | 879 | |
michael@0 | 880 | long |
michael@0 | 881 | AudioStream::GetTimeStretched(void* aBuffer, long aFrames, int64_t &aTimeMs) |
michael@0 | 882 | { |
michael@0 | 883 | long processedFrames = 0; |
michael@0 | 884 | |
michael@0 | 885 | // We need to call the non-locking version, because we already have the lock. |
michael@0 | 886 | if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) { |
michael@0 | 887 | return 0; |
michael@0 | 888 | } |
michael@0 | 889 | |
michael@0 | 890 | uint8_t* wpos = reinterpret_cast<uint8_t*>(aBuffer); |
michael@0 | 891 | double playbackRate = static_cast<double>(mInRate) / mOutRate; |
michael@0 | 892 | uint32_t toPopBytes = FramesToBytes(ceil(aFrames / playbackRate)); |
michael@0 | 893 | uint32_t available = 0; |
michael@0 | 894 | bool lowOnBufferedData = false; |
michael@0 | 895 | do { |
michael@0 | 896 | // Check if we already have enough data in the time stretcher pipeline. |
michael@0 | 897 | if (mTimeStretcher->numSamples() <= static_cast<uint32_t>(aFrames)) { |
michael@0 | 898 | void* input[2]; |
michael@0 | 899 | uint32_t input_size[2]; |
michael@0 | 900 | available = std::min(mBuffer.Length(), toPopBytes); |
michael@0 | 901 | if (available != toPopBytes) { |
michael@0 | 902 | lowOnBufferedData = true; |
michael@0 | 903 | } |
michael@0 | 904 | mBuffer.PopElements(available, &input[0], &input_size[0], |
michael@0 | 905 | &input[1], &input_size[1]); |
michael@0 | 906 | mReadPoint += BytesToFrames(available); |
michael@0 | 907 | for(uint32_t i = 0; i < 2; i++) { |
michael@0 | 908 | mTimeStretcher->putSamples(reinterpret_cast<AudioDataValue*>(input[i]), BytesToFrames(input_size[i])); |
michael@0 | 909 | } |
michael@0 | 910 | } |
michael@0 | 911 | uint32_t receivedFrames = mTimeStretcher->receiveSamples(reinterpret_cast<AudioDataValue*>(wpos), aFrames - processedFrames); |
michael@0 | 912 | wpos += FramesToBytes(receivedFrames); |
michael@0 | 913 | processedFrames += receivedFrames; |
michael@0 | 914 | } while (processedFrames < aFrames && !lowOnBufferedData); |
michael@0 | 915 | |
michael@0 | 916 | GetBufferInsertTime(aTimeMs); |
michael@0 | 917 | |
michael@0 | 918 | return processedFrames; |
michael@0 | 919 | } |
michael@0 | 920 | |
michael@0 | 921 | long |
michael@0 | 922 | AudioStream::DataCallback(void* aBuffer, long aFrames) |
michael@0 | 923 | { |
michael@0 | 924 | MonitorAutoLock mon(mMonitor); |
michael@0 | 925 | uint32_t available = std::min(static_cast<uint32_t>(FramesToBytes(aFrames)), mBuffer.Length()); |
michael@0 | 926 | NS_ABORT_IF_FALSE(available % mBytesPerFrame == 0, "Must copy complete frames"); |
michael@0 | 927 | AudioDataValue* output = reinterpret_cast<AudioDataValue*>(aBuffer); |
michael@0 | 928 | uint32_t underrunFrames = 0; |
michael@0 | 929 | uint32_t servicedFrames = 0; |
michael@0 | 930 | int64_t insertTime; |
michael@0 | 931 | |
michael@0 | 932 | // NOTE: wasapi (others?) can call us back *after* stop()/Shutdown() (mState == SHUTDOWN) |
michael@0 | 933 | // Bug 996162 |
michael@0 | 934 | |
michael@0 | 935 | // callback tells us cubeb succeeded initializing |
michael@0 | 936 | if (mState == STARTED) { |
michael@0 | 937 | // For low-latency streams, we want to minimize any built-up data when |
michael@0 | 938 | // we start getting callbacks. |
michael@0 | 939 | // Simple version - contract on first callback only. |
michael@0 | 940 | if (mLatencyRequest == LowLatency) { |
michael@0 | 941 | #ifdef PR_LOGGING |
michael@0 | 942 | uint32_t old_len = mBuffer.Length(); |
michael@0 | 943 | #endif |
michael@0 | 944 | available = mBuffer.ContractTo(FramesToBytes(aFrames)); |
michael@0 | 945 | #ifdef PR_LOGGING |
michael@0 | 946 | TimeStamp now = TimeStamp::Now(); |
michael@0 | 947 | if (!mStartTime.IsNull()) { |
michael@0 | 948 | int64_t timeMs = (now - mStartTime).ToMilliseconds(); |
michael@0 | 949 | PR_LOG(gAudioStreamLog, PR_LOG_WARNING, |
michael@0 | 950 | ("Stream took %lldms to start after first Write() @ %u", timeMs, mOutRate)); |
michael@0 | 951 | } else { |
michael@0 | 952 | PR_LOG(gAudioStreamLog, PR_LOG_WARNING, |
michael@0 | 953 | ("Stream started before Write() @ %u", mOutRate)); |
michael@0 | 954 | } |
michael@0 | 955 | |
michael@0 | 956 | if (old_len != available) { |
michael@0 | 957 | // Note that we may have dropped samples in Write() as well! |
michael@0 | 958 | PR_LOG(gAudioStreamLog, PR_LOG_WARNING, |
michael@0 | 959 | ("AudioStream %p dropped %u + %u initial frames @ %u", this, |
michael@0 | 960 | mReadPoint, BytesToFrames(old_len - available), mOutRate)); |
michael@0 | 961 | mReadPoint += BytesToFrames(old_len - available); |
michael@0 | 962 | } |
michael@0 | 963 | #endif |
michael@0 | 964 | } |
michael@0 | 965 | mState = RUNNING; |
michael@0 | 966 | } |
michael@0 | 967 | |
michael@0 | 968 | if (available) { |
michael@0 | 969 | // When we are playing a low latency stream, and it is the first time we are |
michael@0 | 970 | // getting data from the buffer, we prefer to add the silence for an |
michael@0 | 971 | // underrun at the beginning of the buffer, so the first buffer is not cut |
michael@0 | 972 | // in half by the silence inserted to compensate for the underrun. |
michael@0 | 973 | if (mInRate == mOutRate) { |
michael@0 | 974 | if (mLatencyRequest == LowLatency && !mWritten) { |
michael@0 | 975 | servicedFrames = GetUnprocessedWithSilencePadding(output, aFrames, insertTime); |
michael@0 | 976 | } else { |
michael@0 | 977 | servicedFrames = GetUnprocessed(output, aFrames, insertTime); |
michael@0 | 978 | } |
michael@0 | 979 | } else { |
michael@0 | 980 | servicedFrames = GetTimeStretched(output, aFrames, insertTime); |
michael@0 | 981 | } |
michael@0 | 982 | float scaled_volume = float(GetVolumeScale() * mVolume); |
michael@0 | 983 | |
michael@0 | 984 | ScaleAudioSamples(output, aFrames * mOutChannels, scaled_volume); |
michael@0 | 985 | |
michael@0 | 986 | NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Must copy complete frames"); |
michael@0 | 987 | |
michael@0 | 988 | // Notify any blocked Write() call that more space is available in mBuffer. |
michael@0 | 989 | mon.NotifyAll(); |
michael@0 | 990 | } else { |
michael@0 | 991 | GetBufferInsertTime(insertTime); |
michael@0 | 992 | } |
michael@0 | 993 | |
michael@0 | 994 | underrunFrames = aFrames - servicedFrames; |
michael@0 | 995 | |
michael@0 | 996 | if (mState != DRAINING) { |
michael@0 | 997 | uint8_t* rpos = static_cast<uint8_t*>(aBuffer) + FramesToBytes(aFrames - underrunFrames); |
michael@0 | 998 | memset(rpos, 0, FramesToBytes(underrunFrames)); |
michael@0 | 999 | if (underrunFrames) { |
michael@0 | 1000 | PR_LOG(gAudioStreamLog, PR_LOG_WARNING, |
michael@0 | 1001 | ("AudioStream %p lost %d frames", this, underrunFrames)); |
michael@0 | 1002 | } |
michael@0 | 1003 | mLostFrames += underrunFrames; |
michael@0 | 1004 | servicedFrames += underrunFrames; |
michael@0 | 1005 | } |
michael@0 | 1006 | |
michael@0 | 1007 | WriteDumpFile(mDumpFile, this, aFrames, aBuffer); |
michael@0 | 1008 | // Don't log if we're not interested or if the stream is inactive |
michael@0 | 1009 | if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG) && |
michael@0 | 1010 | mState != SHUTDOWN && |
michael@0 | 1011 | insertTime != INT64_MAX && servicedFrames > underrunFrames) { |
michael@0 | 1012 | uint32_t latency = UINT32_MAX; |
michael@0 | 1013 | if (cubeb_stream_get_latency(mCubebStream, &latency)) { |
michael@0 | 1014 | NS_WARNING("Could not get latency from cubeb."); |
michael@0 | 1015 | } |
michael@0 | 1016 | TimeStamp now = TimeStamp::Now(); |
michael@0 | 1017 | |
michael@0 | 1018 | mLatencyLog->Log(AsyncLatencyLogger::AudioStream, reinterpret_cast<uint64_t>(this), |
michael@0 | 1019 | insertTime, now); |
michael@0 | 1020 | mLatencyLog->Log(AsyncLatencyLogger::Cubeb, reinterpret_cast<uint64_t>(mCubebStream.get()), |
michael@0 | 1021 | (latency * 1000) / mOutRate, now); |
michael@0 | 1022 | } |
michael@0 | 1023 | |
michael@0 | 1024 | mAudioClock.UpdateWritePosition(servicedFrames); |
michael@0 | 1025 | return servicedFrames; |
michael@0 | 1026 | } |
michael@0 | 1027 | |
michael@0 | 1028 | void |
michael@0 | 1029 | AudioStream::StateCallback(cubeb_state aState) |
michael@0 | 1030 | { |
michael@0 | 1031 | MonitorAutoLock mon(mMonitor); |
michael@0 | 1032 | if (aState == CUBEB_STATE_DRAINED) { |
michael@0 | 1033 | mState = DRAINED; |
michael@0 | 1034 | } else if (aState == CUBEB_STATE_ERROR) { |
michael@0 | 1035 | LOG(("AudioStream::StateCallback() state %d cubeb error", mState)); |
michael@0 | 1036 | mState = ERRORED; |
michael@0 | 1037 | } |
michael@0 | 1038 | mon.NotifyAll(); |
michael@0 | 1039 | } |
michael@0 | 1040 | |
michael@0 | 1041 | AudioClock::AudioClock(AudioStream* aStream) |
michael@0 | 1042 | :mAudioStream(aStream), |
michael@0 | 1043 | mOldOutRate(0), |
michael@0 | 1044 | mBasePosition(0), |
michael@0 | 1045 | mBaseOffset(0), |
michael@0 | 1046 | mOldBaseOffset(0), |
michael@0 | 1047 | mOldBasePosition(0), |
michael@0 | 1048 | mPlaybackRateChangeOffset(0), |
michael@0 | 1049 | mPreviousPosition(0), |
michael@0 | 1050 | mWritten(0), |
michael@0 | 1051 | mOutRate(0), |
michael@0 | 1052 | mInRate(0), |
michael@0 | 1053 | mPreservesPitch(true), |
michael@0 | 1054 | mCompensatingLatency(false) |
michael@0 | 1055 | {} |
michael@0 | 1056 | |
michael@0 | 1057 | void AudioClock::Init() |
michael@0 | 1058 | { |
michael@0 | 1059 | mOutRate = mAudioStream->GetRate(); |
michael@0 | 1060 | mInRate = mAudioStream->GetRate(); |
michael@0 | 1061 | mOldOutRate = mOutRate; |
michael@0 | 1062 | } |
michael@0 | 1063 | |
michael@0 | 1064 | void AudioClock::UpdateWritePosition(uint32_t aCount) |
michael@0 | 1065 | { |
michael@0 | 1066 | mWritten += aCount; |
michael@0 | 1067 | } |
michael@0 | 1068 | |
michael@0 | 1069 | uint64_t AudioClock::GetPositionUnlocked() |
michael@0 | 1070 | { |
michael@0 | 1071 | // GetPositionInFramesUnlocked() asserts it owns the monitor |
michael@0 | 1072 | int64_t position = mAudioStream->GetPositionInFramesUnlocked(); |
michael@0 | 1073 | int64_t diffOffset; |
michael@0 | 1074 | NS_ASSERTION(position < 0 || (mInRate != 0 && mOutRate != 0), "AudioClock not initialized."); |
michael@0 | 1075 | if (position >= 0) { |
michael@0 | 1076 | if (position < mPlaybackRateChangeOffset) { |
michael@0 | 1077 | // See if we are still playing frames pushed with the old playback rate in |
michael@0 | 1078 | // the backend. If we are, use the old output rate to compute the |
michael@0 | 1079 | // position. |
michael@0 | 1080 | mCompensatingLatency = true; |
michael@0 | 1081 | diffOffset = position - mOldBaseOffset; |
michael@0 | 1082 | position = static_cast<uint64_t>(mOldBasePosition + |
michael@0 | 1083 | static_cast<float>(USECS_PER_S * diffOffset) / mOldOutRate); |
michael@0 | 1084 | mPreviousPosition = position; |
michael@0 | 1085 | return position; |
michael@0 | 1086 | } |
michael@0 | 1087 | |
michael@0 | 1088 | if (mCompensatingLatency) { |
michael@0 | 1089 | diffOffset = position - mPlaybackRateChangeOffset; |
michael@0 | 1090 | mCompensatingLatency = false; |
michael@0 | 1091 | mBasePosition = mPreviousPosition; |
michael@0 | 1092 | } else { |
michael@0 | 1093 | diffOffset = position - mPlaybackRateChangeOffset; |
michael@0 | 1094 | } |
michael@0 | 1095 | position = static_cast<uint64_t>(mBasePosition + |
michael@0 | 1096 | (static_cast<float>(USECS_PER_S * diffOffset) / mOutRate)); |
michael@0 | 1097 | return position; |
michael@0 | 1098 | } |
michael@0 | 1099 | return UINT64_MAX; |
michael@0 | 1100 | } |
michael@0 | 1101 | |
michael@0 | 1102 | uint64_t AudioClock::GetPositionInFrames() |
michael@0 | 1103 | { |
michael@0 | 1104 | return (GetPositionUnlocked() * mOutRate) / USECS_PER_S; |
michael@0 | 1105 | } |
michael@0 | 1106 | |
michael@0 | 1107 | void AudioClock::SetPlaybackRateUnlocked(double aPlaybackRate) |
michael@0 | 1108 | { |
michael@0 | 1109 | // GetPositionInFramesUnlocked() asserts it owns the monitor |
michael@0 | 1110 | int64_t position = mAudioStream->GetPositionInFramesUnlocked(); |
michael@0 | 1111 | if (position > mPlaybackRateChangeOffset) { |
michael@0 | 1112 | mOldBasePosition = mBasePosition; |
michael@0 | 1113 | mBasePosition = GetPositionUnlocked(); |
michael@0 | 1114 | mOldBaseOffset = mPlaybackRateChangeOffset; |
michael@0 | 1115 | mBaseOffset = position; |
michael@0 | 1116 | mPlaybackRateChangeOffset = mWritten; |
michael@0 | 1117 | mOldOutRate = mOutRate; |
michael@0 | 1118 | mOutRate = static_cast<int>(mInRate / aPlaybackRate); |
michael@0 | 1119 | } else { |
michael@0 | 1120 | // The playbackRate has been changed before the end of the latency |
michael@0 | 1121 | // compensation phase. We don't update the mOld* variable. That way, the |
michael@0 | 1122 | // last playbackRate set is taken into account. |
michael@0 | 1123 | mBasePosition = GetPositionUnlocked(); |
michael@0 | 1124 | mBaseOffset = position; |
michael@0 | 1125 | mPlaybackRateChangeOffset = mWritten; |
michael@0 | 1126 | mOutRate = static_cast<int>(mInRate / aPlaybackRate); |
michael@0 | 1127 | } |
michael@0 | 1128 | } |
michael@0 | 1129 | |
michael@0 | 1130 | double AudioClock::GetPlaybackRate() |
michael@0 | 1131 | { |
michael@0 | 1132 | return static_cast<double>(mInRate) / mOutRate; |
michael@0 | 1133 | } |
michael@0 | 1134 | |
michael@0 | 1135 | void AudioClock::SetPreservesPitch(bool aPreservesPitch) |
michael@0 | 1136 | { |
michael@0 | 1137 | mPreservesPitch = aPreservesPitch; |
michael@0 | 1138 | } |
michael@0 | 1139 | |
michael@0 | 1140 | bool AudioClock::GetPreservesPitch() |
michael@0 | 1141 | { |
michael@0 | 1142 | return mPreservesPitch; |
michael@0 | 1143 | } |
michael@0 | 1144 | } // namespace mozilla |