content/media/AudioStream.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial