LCOV - code coverage report
Current view: top level - dom/media - AudioStream.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 320 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 46 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       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 <string.h>
       9             : #include "mozilla/Logging.h"
      10             : #include "prdtoa.h"
      11             : #include "AudioStream.h"
      12             : #include "VideoUtils.h"
      13             : #include "mozilla/Monitor.h"
      14             : #include "mozilla/Mutex.h"
      15             : #include "mozilla/Sprintf.h"
      16             : #include <algorithm>
      17             : #include "mozilla/Telemetry.h"
      18             : #include "CubebUtils.h"
      19             : #include "nsPrintfCString.h"
      20             : #include "gfxPrefs.h"
      21             : #include "AudioConverter.h"
      22             : 
      23             : namespace mozilla {
      24             : 
      25             : #undef LOG
      26             : #undef LOGW
      27             : 
      28             : LazyLogModule gAudioStreamLog("AudioStream");
      29             : // For simple logs
      30             : #define LOG(x, ...) MOZ_LOG(gAudioStreamLog, mozilla::LogLevel::Debug, ("%p " x, this, ##__VA_ARGS__))
      31             : #define LOGW(x, ...) MOZ_LOG(gAudioStreamLog, mozilla::LogLevel::Warning, ("%p " x, this, ##__VA_ARGS__))
      32             : 
      33             : /**
      34             :  * Keep a list of frames sent to the audio engine in each DataCallback along
      35             :  * with the playback rate at the moment. Since the playback rate and number of
      36             :  * underrun frames can vary in each callback. We need to keep the whole history
      37             :  * in order to calculate the playback position of the audio engine correctly.
      38             :  */
      39           0 : class FrameHistory {
      40             :   struct Chunk {
      41             :     uint32_t servicedFrames;
      42             :     uint32_t totalFrames;
      43             :     uint32_t rate;
      44             :   };
      45             : 
      46             :   template <typename T>
      47           0 :   static T FramesToUs(uint32_t frames, int rate) {
      48           0 :     return static_cast<T>(frames) * USECS_PER_S / rate;
      49             :   }
      50             : public:
      51           0 :   FrameHistory()
      52           0 :     : mBaseOffset(0), mBasePosition(0) {}
      53             : 
      54           0 :   void Append(uint32_t aServiced, uint32_t aUnderrun, uint32_t aRate) {
      55             :     /* In most case where playback rate stays the same and we don't underrun
      56             :      * frames, we are able to merge chunks to avoid lose of precision to add up
      57             :      * in compressing chunks into |mBaseOffset| and |mBasePosition|.
      58             :      */
      59           0 :     if (!mChunks.IsEmpty()) {
      60           0 :       Chunk& c = mChunks.LastElement();
      61             :       // 2 chunks (c1 and c2) can be merged when rate is the same and
      62             :       // adjacent frames are zero. That is, underrun frames in c1 are zero
      63             :       // or serviced frames in c2 are zero.
      64           0 :       if (c.rate == aRate &&
      65           0 :           (c.servicedFrames == c.totalFrames ||
      66             :            aServiced == 0)) {
      67           0 :         c.servicedFrames += aServiced;
      68           0 :         c.totalFrames += aServiced + aUnderrun;
      69           0 :         return;
      70             :       }
      71             :     }
      72           0 :     Chunk* p = mChunks.AppendElement();
      73           0 :     p->servicedFrames = aServiced;
      74           0 :     p->totalFrames = aServiced + aUnderrun;
      75           0 :     p->rate = aRate;
      76             :   }
      77             : 
      78             :   /**
      79             :    * @param frames The playback position in frames of the audio engine.
      80             :    * @return The playback position in microseconds of the audio engine,
      81             :    *         adjusted by playback rate changes and underrun frames.
      82             :    */
      83           0 :   int64_t GetPosition(int64_t frames) {
      84             :     // playback position should not go backward.
      85           0 :     MOZ_ASSERT(frames >= mBaseOffset);
      86             :     while (true) {
      87           0 :       if (mChunks.IsEmpty()) {
      88           0 :         return mBasePosition;
      89             :       }
      90           0 :       const Chunk& c = mChunks[0];
      91           0 :       if (frames <= mBaseOffset + c.totalFrames) {
      92           0 :         uint32_t delta = frames - mBaseOffset;
      93           0 :         delta = std::min(delta, c.servicedFrames);
      94           0 :         return static_cast<int64_t>(mBasePosition) +
      95           0 :                FramesToUs<int64_t>(delta, c.rate);
      96             :       }
      97             :       // Since the playback position of the audio engine will not go backward,
      98             :       // we are able to compress chunks so that |mChunks| won't grow unlimitedly.
      99             :       // Note that we lose precision in converting integers into floats and
     100             :       // inaccuracy will accumulate over time. However, for a 24hr long,
     101             :       // sample rate = 44.1k file, the error will be less than 1 microsecond
     102             :       // after playing 24 hours. So we are fine with that.
     103           0 :       mBaseOffset += c.totalFrames;
     104           0 :       mBasePosition += FramesToUs<double>(c.servicedFrames, c.rate);
     105           0 :       mChunks.RemoveElementAt(0);
     106           0 :     }
     107             :   }
     108             : private:
     109             :   AutoTArray<Chunk, 7> mChunks;
     110             :   int64_t mBaseOffset;
     111             :   double mBasePosition;
     112             : };
     113             : 
     114           0 : AudioStream::AudioStream(DataSource& aSource)
     115             :   : mMonitor("AudioStream")
     116             :   , mChannels(0)
     117             :   , mOutChannels(0)
     118             :   , mTimeStretcher(nullptr)
     119             :   , mDumpFile(nullptr)
     120             :   , mState(INITIALIZED)
     121           0 :   , mDataSource(aSource)
     122             : {
     123           0 : }
     124             : 
     125           0 : AudioStream::~AudioStream()
     126             : {
     127           0 :   LOG("deleted, state %d", mState);
     128           0 :   MOZ_ASSERT(mState == SHUTDOWN && !mCubebStream,
     129             :              "Should've called Shutdown() before deleting an AudioStream");
     130           0 :   if (mDumpFile) {
     131           0 :     fclose(mDumpFile);
     132             :   }
     133           0 :   if (mTimeStretcher) {
     134           0 :     soundtouch::destroySoundTouchObj(mTimeStretcher);
     135             :   }
     136           0 : }
     137             : 
     138             : size_t
     139           0 : AudioStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
     140             : {
     141           0 :   size_t amount = aMallocSizeOf(this);
     142             : 
     143             :   // Possibly add in the future:
     144             :   // - mTimeStretcher
     145             :   // - mCubebStream
     146             : 
     147           0 :   return amount;
     148             : }
     149             : 
     150           0 : nsresult AudioStream::EnsureTimeStretcherInitializedUnlocked()
     151             : {
     152           0 :   mMonitor.AssertCurrentThreadOwns();
     153           0 :   if (!mTimeStretcher) {
     154           0 :     mTimeStretcher = soundtouch::createSoundTouchObj();
     155           0 :     mTimeStretcher->setSampleRate(mAudioClock.GetInputRate());
     156           0 :     mTimeStretcher->setChannels(mOutChannels);
     157           0 :     mTimeStretcher->setPitch(1.0);
     158             :   }
     159           0 :   return NS_OK;
     160             : }
     161             : 
     162           0 : nsresult AudioStream::SetPlaybackRate(double aPlaybackRate)
     163             : {
     164             :   // MUST lock since the rate transposer is used from the cubeb callback,
     165             :   // and rate changes can cause the buffer to be reallocated
     166           0 :   MonitorAutoLock mon(mMonitor);
     167             : 
     168           0 :   NS_ASSERTION(aPlaybackRate > 0.0,
     169             :                "Can't handle negative or null playbackrate in the AudioStream.");
     170             :   // Avoid instantiating the resampler if we are not changing the playback rate.
     171             :   // GetPreservesPitch/SetPreservesPitch don't need locking before calling
     172           0 :   if (aPlaybackRate == mAudioClock.GetPlaybackRate()) {
     173           0 :     return NS_OK;
     174             :   }
     175             : 
     176           0 :   if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) {
     177           0 :     return NS_ERROR_FAILURE;
     178             :   }
     179             : 
     180           0 :   mAudioClock.SetPlaybackRate(aPlaybackRate);
     181             : 
     182           0 :   if (mAudioClock.GetPreservesPitch()) {
     183           0 :     mTimeStretcher->setTempo(aPlaybackRate);
     184           0 :     mTimeStretcher->setRate(1.0f);
     185             :   } else {
     186           0 :     mTimeStretcher->setTempo(1.0f);
     187           0 :     mTimeStretcher->setRate(aPlaybackRate);
     188             :   }
     189           0 :   return NS_OK;
     190             : }
     191             : 
     192           0 : nsresult AudioStream::SetPreservesPitch(bool aPreservesPitch)
     193             : {
     194             :   // MUST lock since the rate transposer is used from the cubeb callback,
     195             :   // and rate changes can cause the buffer to be reallocated
     196           0 :   MonitorAutoLock mon(mMonitor);
     197             : 
     198             :   // Avoid instantiating the timestretcher instance if not needed.
     199           0 :   if (aPreservesPitch == mAudioClock.GetPreservesPitch()) {
     200           0 :     return NS_OK;
     201             :   }
     202             : 
     203           0 :   if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) {
     204           0 :     return NS_ERROR_FAILURE;
     205             :   }
     206             : 
     207           0 :   if (aPreservesPitch == true) {
     208           0 :     mTimeStretcher->setTempo(mAudioClock.GetPlaybackRate());
     209           0 :     mTimeStretcher->setRate(1.0f);
     210             :   } else {
     211           0 :     mTimeStretcher->setTempo(1.0f);
     212           0 :     mTimeStretcher->setRate(mAudioClock.GetPlaybackRate());
     213             :   }
     214             : 
     215           0 :   mAudioClock.SetPreservesPitch(aPreservesPitch);
     216             : 
     217           0 :   return NS_OK;
     218             : }
     219             : 
     220           0 : static void SetUint16LE(uint8_t* aDest, uint16_t aValue)
     221             : {
     222           0 :   aDest[0] = aValue & 0xFF;
     223           0 :   aDest[1] = aValue >> 8;
     224           0 : }
     225             : 
     226           0 : static void SetUint32LE(uint8_t* aDest, uint32_t aValue)
     227             : {
     228           0 :   SetUint16LE(aDest, aValue & 0xFFFF);
     229           0 :   SetUint16LE(aDest + 2, aValue >> 16);
     230           0 : }
     231             : 
     232             : static FILE*
     233           0 : OpenDumpFile(uint32_t aChannels, uint32_t aRate)
     234             : {
     235             :   /**
     236             :    * When MOZ_DUMP_AUDIO is set in the environment (to anything),
     237             :    * we'll drop a series of files in the current working directory named
     238             :    * dumped-audio-<nnn>.wav, one per AudioStream created, containing
     239             :    * the audio for the stream including any skips due to underruns.
     240             :    */
     241             :   static Atomic<int> gDumpedAudioCount(0);
     242             : 
     243           0 :   if (!getenv("MOZ_DUMP_AUDIO"))
     244           0 :     return nullptr;
     245             :   char buf[100];
     246           0 :   SprintfLiteral(buf, "dumped-audio-%d.wav", ++gDumpedAudioCount);
     247           0 :   FILE* f = fopen(buf, "wb");
     248           0 :   if (!f)
     249           0 :     return nullptr;
     250             : 
     251             :   uint8_t header[] = {
     252             :     // RIFF header
     253             :     0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45,
     254             :     // fmt chunk. We always write 16-bit samples.
     255             :     0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFF, 0xFF,
     256             :     0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x10, 0x00,
     257             :     // data chunk
     258             :     0x64, 0x61, 0x74, 0x61, 0xFE, 0xFF, 0xFF, 0x7F
     259           0 :   };
     260             :   static const int CHANNEL_OFFSET = 22;
     261             :   static const int SAMPLE_RATE_OFFSET = 24;
     262             :   static const int BLOCK_ALIGN_OFFSET = 32;
     263           0 :   SetUint16LE(header + CHANNEL_OFFSET, aChannels);
     264           0 :   SetUint32LE(header + SAMPLE_RATE_OFFSET, aRate);
     265           0 :   SetUint16LE(header + BLOCK_ALIGN_OFFSET, aChannels * 2);
     266           0 :   fwrite(header, sizeof(header), 1, f);
     267             : 
     268           0 :   return f;
     269             : }
     270             : 
     271             : template <typename T>
     272             : typename EnableIf<IsSame<T, int16_t>::value, void>::Type
     273             : WriteDumpFileHelper(T* aInput, size_t aSamples, FILE* aFile) {
     274             :   fwrite(aInput, sizeof(T), aSamples, aFile);
     275             : }
     276             : 
     277             : template <typename T>
     278             : typename EnableIf<IsSame<T, float>::value, void>::Type
     279           0 : WriteDumpFileHelper(T* aInput, size_t aSamples, FILE* aFile) {
     280           0 :   AutoTArray<uint8_t, 1024*2> buf;
     281           0 :   buf.SetLength(aSamples*2);
     282           0 :   uint8_t* output = buf.Elements();
     283           0 :   for (uint32_t i = 0; i < aSamples; ++i) {
     284           0 :     SetUint16LE(output + i*2, int16_t(aInput[i]*32767.0f));
     285             :   }
     286           0 :   fwrite(output, 2, aSamples, aFile);
     287           0 :   fflush(aFile);
     288           0 : }
     289             : 
     290             : static void
     291           0 : WriteDumpFile(FILE* aDumpFile, AudioStream* aStream, uint32_t aFrames,
     292             :               void* aBuffer)
     293             : {
     294           0 :   if (!aDumpFile)
     295           0 :     return;
     296             : 
     297           0 :   uint32_t samples = aStream->GetOutChannels()*aFrames;
     298             : 
     299             :   using SampleT = AudioSampleTraits<AUDIO_OUTPUT_FORMAT>::Type;
     300           0 :   WriteDumpFileHelper(reinterpret_cast<SampleT*>(aBuffer), samples, aDumpFile);
     301             : }
     302             : 
     303             : template <AudioSampleFormat N>
     304             : struct ToCubebFormat {
     305             :   static const cubeb_sample_format value = CUBEB_SAMPLE_FLOAT32NE;
     306             : };
     307             : 
     308             : template <>
     309             : struct ToCubebFormat<AUDIO_FORMAT_S16> {
     310             :   static const cubeb_sample_format value = CUBEB_SAMPLE_S16NE;
     311             : };
     312             : 
     313             : template <typename Function, typename... Args>
     314           0 : int AudioStream::InvokeCubeb(Function aFunction, Args&&... aArgs)
     315             : {
     316           0 :   MonitorAutoUnlock mon(mMonitor);
     317           0 :   return aFunction(mCubebStream.get(), Forward<Args>(aArgs)...);
     318             : }
     319             : 
     320             : nsresult
     321           0 : AudioStream::Init(uint32_t aNumChannels, uint32_t aChannelMap, uint32_t aRate,
     322             :                   const dom::AudioChannel aAudioChannel)
     323             : {
     324           0 :   auto startTime = TimeStamp::Now();
     325             : 
     326           0 :   LOG("%s channels: %d, rate: %d", __FUNCTION__, aNumChannels, aRate);
     327           0 :   mChannels = aNumChannels;
     328           0 :   mOutChannels = aNumChannels;
     329             : 
     330           0 :   mDumpFile = OpenDumpFile(aNumChannels, aRate);
     331             : 
     332             :   cubeb_stream_params params;
     333           0 :   params.rate = aRate;
     334           0 :   params.channels = mOutChannels;
     335           0 :   params.layout = CubebUtils::ConvertChannelMapToCubebLayout(aChannelMap);
     336             : #if defined(__ANDROID__)
     337             : #if defined(MOZ_B2G)
     338             :   params.stream_type = CubebUtils::ConvertChannelToCubebType(aAudioChannel);
     339             : #else
     340             :   params.stream_type = CUBEB_STREAM_TYPE_MUSIC;
     341             : #endif
     342             : 
     343             :   if (params.stream_type == CUBEB_STREAM_TYPE_MAX) {
     344             :     return NS_ERROR_INVALID_ARG;
     345             :   }
     346             : #endif
     347             : 
     348           0 :   params.format = ToCubebFormat<AUDIO_OUTPUT_FORMAT>::value;
     349           0 :   mAudioClock.Init(aRate);
     350             : 
     351           0 :   cubeb* cubebContext = CubebUtils::GetCubebContext();
     352           0 :   if (!cubebContext) {
     353           0 :     NS_WARNING("Can't get cubeb context!");
     354           0 :     CubebUtils::ReportCubebStreamInitFailure(true);
     355           0 :     return NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR;
     356             :   }
     357             : 
     358           0 :   return OpenCubeb(cubebContext, params, startTime, CubebUtils::GetFirstStream());
     359             : }
     360             : 
     361             : nsresult
     362           0 : AudioStream::OpenCubeb(cubeb* aContext, cubeb_stream_params& aParams,
     363             :                        TimeStamp aStartTime, bool aIsFirst)
     364             : {
     365           0 :   MOZ_ASSERT(aContext);
     366             : 
     367           0 :   cubeb_stream* stream = nullptr;
     368             :   /* Convert from milliseconds to frames. */
     369             :   uint32_t latency_frames =
     370           0 :     CubebUtils::GetCubebPlaybackLatencyInMilliseconds() * aParams.rate / 1000;
     371           0 :   if (cubeb_stream_init(aContext, &stream, "AudioStream",
     372             :                         nullptr, nullptr, nullptr, &aParams,
     373             :                         latency_frames,
     374             :                         DataCallback_S, StateCallback_S, this) == CUBEB_OK) {
     375           0 :     mCubebStream.reset(stream);
     376           0 :     CubebUtils::ReportCubebBackendUsed();
     377             :   } else {
     378           0 :     NS_WARNING(nsPrintfCString("AudioStream::OpenCubeb() %p failed to init cubeb", this).get());
     379           0 :     CubebUtils::ReportCubebStreamInitFailure(aIsFirst);
     380           0 :     return NS_ERROR_FAILURE;
     381             :   }
     382             : 
     383           0 :   TimeDuration timeDelta = TimeStamp::Now() - aStartTime;
     384           0 :   LOG("creation time %sfirst: %u ms", aIsFirst ? "" : "not ",
     385             :       (uint32_t) timeDelta.ToMilliseconds());
     386           0 :   Telemetry::Accumulate(aIsFirst ? Telemetry::AUDIOSTREAM_FIRST_OPEN_MS :
     387           0 :       Telemetry::AUDIOSTREAM_LATER_OPEN_MS, timeDelta.ToMilliseconds());
     388             : 
     389           0 :   return NS_OK;
     390             : }
     391             : 
     392             : void
     393           0 : AudioStream::SetVolume(double aVolume)
     394             : {
     395           0 :   MOZ_ASSERT(aVolume >= 0.0 && aVolume <= 1.0, "Invalid volume");
     396             : 
     397           0 :   if (cubeb_stream_set_volume(mCubebStream.get(), aVolume * CubebUtils::GetVolumeScale()) != CUBEB_OK) {
     398           0 :     NS_WARNING("Could not change volume on cubeb stream.");
     399             :   }
     400           0 : }
     401             : 
     402             : void
     403           0 : AudioStream::Start()
     404             : {
     405           0 :   MonitorAutoLock mon(mMonitor);
     406           0 :   MOZ_ASSERT(mState == INITIALIZED);
     407           0 :   mState = STARTED;
     408           0 :   auto r = InvokeCubeb(cubeb_stream_start);
     409           0 :   if (r != CUBEB_OK) {
     410           0 :     mState = ERRORED;
     411             :   }
     412           0 :   LOG("started, state %s", mState == STARTED ? "STARTED" : mState == DRAINED ? "DRAINED" : "ERRORED");
     413           0 : }
     414             : 
     415             : void
     416           0 : AudioStream::Pause()
     417             : {
     418           0 :   MonitorAutoLock mon(mMonitor);
     419           0 :   MOZ_ASSERT(mState != INITIALIZED, "Must be Start()ed.");
     420           0 :   MOZ_ASSERT(mState != STOPPED, "Already Pause()ed.");
     421           0 :   MOZ_ASSERT(mState != SHUTDOWN, "Already Shutdown()ed.");
     422             : 
     423             :   // Do nothing if we are already drained or errored.
     424           0 :   if (mState == DRAINED || mState == ERRORED) {
     425           0 :     return;
     426             :   }
     427             : 
     428           0 :   if (InvokeCubeb(cubeb_stream_stop) != CUBEB_OK) {
     429           0 :     mState = ERRORED;
     430           0 :   } else if (mState != DRAINED && mState != ERRORED) {
     431             :     // Don't transition to other states if we are already
     432             :     // drained or errored.
     433           0 :     mState = STOPPED;
     434             :   }
     435             : }
     436             : 
     437             : void
     438           0 : AudioStream::Resume()
     439             : {
     440           0 :   MonitorAutoLock mon(mMonitor);
     441           0 :   MOZ_ASSERT(mState != INITIALIZED, "Must be Start()ed.");
     442           0 :   MOZ_ASSERT(mState != STARTED, "Already Start()ed.");
     443           0 :   MOZ_ASSERT(mState != SHUTDOWN, "Already Shutdown()ed.");
     444             : 
     445             :   // Do nothing if we are already drained or errored.
     446           0 :   if (mState == DRAINED || mState == ERRORED) {
     447           0 :     return;
     448             :   }
     449             : 
     450           0 :   if (InvokeCubeb(cubeb_stream_start) != CUBEB_OK) {
     451           0 :     mState = ERRORED;
     452           0 :   } else if (mState != DRAINED && mState != ERRORED) {
     453             :     // Don't transition to other states if we are already
     454             :     // drained or errored.
     455           0 :     mState = STARTED;
     456             :   }
     457             : }
     458             : 
     459             : void
     460           0 : AudioStream::Shutdown()
     461             : {
     462           0 :   MonitorAutoLock mon(mMonitor);
     463           0 :   LOG("Shutdown, state %d", mState);
     464             : 
     465           0 :   if (mCubebStream) {
     466           0 :     MonitorAutoUnlock mon(mMonitor);
     467             :     // Force stop to put the cubeb stream in a stable state before deletion.
     468           0 :     cubeb_stream_stop(mCubebStream.get());
     469             :     // Must not try to shut down cubeb from within the lock!  wasapi may still
     470             :     // call our callback after Pause()/stop()!?! Bug 996162
     471           0 :     mCubebStream.reset();
     472             :   }
     473             : 
     474           0 :   mState = SHUTDOWN;
     475           0 : }
     476             : 
     477             : int64_t
     478           0 : AudioStream::GetPosition()
     479             : {
     480           0 :   MonitorAutoLock mon(mMonitor);
     481           0 :   int64_t frames = GetPositionInFramesUnlocked();
     482           0 :   return frames >= 0 ? mAudioClock.GetPosition(frames) : -1;
     483             : }
     484             : 
     485             : int64_t
     486           0 : AudioStream::GetPositionInFrames()
     487             : {
     488           0 :   MonitorAutoLock mon(mMonitor);
     489           0 :   int64_t frames = GetPositionInFramesUnlocked();
     490           0 :   return frames >= 0 ? mAudioClock.GetPositionInFrames(frames) : -1;
     491             : }
     492             : 
     493             : int64_t
     494           0 : AudioStream::GetPositionInFramesUnlocked()
     495             : {
     496           0 :   mMonitor.AssertCurrentThreadOwns();
     497             : 
     498           0 :   if (mState == ERRORED) {
     499           0 :     return -1;
     500             :   }
     501             : 
     502           0 :   uint64_t position = 0;
     503           0 :   if (InvokeCubeb(cubeb_stream_get_position, &position) != CUBEB_OK) {
     504           0 :     return -1;
     505             :   }
     506           0 :   return std::min<uint64_t>(position, INT64_MAX);
     507             : }
     508             : 
     509             : bool
     510           0 : AudioStream::IsValidAudioFormat(Chunk* aChunk)
     511             : {
     512           0 :   if (aChunk->Rate() != mAudioClock.GetInputRate()) {
     513           0 :     LOGW("mismatched sample %u, mInRate=%u", aChunk->Rate(), mAudioClock.GetInputRate());
     514           0 :     return false;
     515             :   }
     516             : 
     517           0 :   if (aChunk->Channels() > 8) {
     518           0 :     return false;
     519             :   }
     520             : 
     521           0 :   return true;
     522             : }
     523             : 
     524             : void
     525           0 : AudioStream::GetUnprocessed(AudioBufferWriter& aWriter)
     526             : {
     527           0 :   mMonitor.AssertCurrentThreadOwns();
     528             : 
     529             :   // Flush the timestretcher pipeline, if we were playing using a playback rate
     530             :   // other than 1.0.
     531           0 :   if (mTimeStretcher && mTimeStretcher->numSamples()) {
     532           0 :     auto timeStretcher = mTimeStretcher;
     533           0 :     aWriter.Write([timeStretcher] (AudioDataValue* aPtr, uint32_t aFrames) {
     534           0 :       return timeStretcher->receiveSamples(aPtr, aFrames);
     535           0 :     }, aWriter.Available());
     536             : 
     537             :     // TODO: There might be still unprocessed samples in the stretcher.
     538             :     // We should either remove or flush them so they won't be in the output
     539             :     // next time we switch a playback rate other than 1.0.
     540           0 :     NS_WARNING_ASSERTION(
     541             :       mTimeStretcher->numUnprocessedSamples() == 0, "no samples");
     542             :   }
     543             : 
     544           0 :   while (aWriter.Available() > 0) {
     545           0 :     UniquePtr<Chunk> c = mDataSource.PopFrames(aWriter.Available());
     546           0 :     if (c->Frames() == 0) {
     547           0 :       break;
     548             :     }
     549           0 :     MOZ_ASSERT(c->Frames() <= aWriter.Available());
     550           0 :     if (IsValidAudioFormat(c.get())) {
     551           0 :       aWriter.Write(c->Data(), c->Frames());
     552             :     } else {
     553             :       // Write silence if invalid format.
     554           0 :       aWriter.WriteZeros(c->Frames());
     555             :     }
     556             :   }
     557           0 : }
     558             : 
     559             : void
     560           0 : AudioStream::GetTimeStretched(AudioBufferWriter& aWriter)
     561             : {
     562           0 :   mMonitor.AssertCurrentThreadOwns();
     563             : 
     564             :   // We need to call the non-locking version, because we already have the lock.
     565           0 :   if (EnsureTimeStretcherInitializedUnlocked() != NS_OK) {
     566           0 :     return;
     567             :   }
     568             : 
     569             :   uint32_t toPopFrames =
     570           0 :     ceil(aWriter.Available() * mAudioClock.GetPlaybackRate());
     571             : 
     572           0 :   while (mTimeStretcher->numSamples() < aWriter.Available()) {
     573           0 :     UniquePtr<Chunk> c = mDataSource.PopFrames(toPopFrames);
     574           0 :     if (c->Frames() == 0) {
     575           0 :       break;
     576             :     }
     577           0 :     MOZ_ASSERT(c->Frames() <= toPopFrames);
     578           0 :     if (IsValidAudioFormat(c.get())) {
     579           0 :       mTimeStretcher->putSamples(c->Data(), c->Frames());
     580             :     } else {
     581             :       // Write silence if invalid format.
     582           0 :       AutoTArray<AudioDataValue, 1000> buf;
     583           0 :       auto size = CheckedUint32(mOutChannels) * c->Frames();
     584           0 :       if (!size.isValid()) {
     585             :         // The overflow should not happen in normal case.
     586           0 :         LOGW("Invalid member data: %d channels, %d frames", mOutChannels, c->Frames());
     587           0 :         return;
     588             :       }
     589           0 :       buf.SetLength(size.value());
     590           0 :       size = size * sizeof(AudioDataValue);
     591           0 :       if (!size.isValid()) {
     592           0 :         LOGW("The required memory size is too large.");
     593           0 :         return;
     594             :       }
     595           0 :       memset(buf.Elements(), 0, size.value());
     596           0 :       mTimeStretcher->putSamples(buf.Elements(), c->Frames());
     597             :     }
     598             :   }
     599             : 
     600           0 :   auto timeStretcher = mTimeStretcher;
     601           0 :   aWriter.Write([timeStretcher] (AudioDataValue* aPtr, uint32_t aFrames) {
     602           0 :     return timeStretcher->receiveSamples(aPtr, aFrames);
     603           0 :   }, aWriter.Available());
     604             : }
     605             : 
     606             : long
     607           0 : AudioStream::DataCallback(void* aBuffer, long aFrames)
     608             : {
     609           0 :   MonitorAutoLock mon(mMonitor);
     610           0 :   MOZ_ASSERT(mState != SHUTDOWN, "No data callback after shutdown");
     611             : 
     612             :   auto writer = AudioBufferWriter(
     613           0 :     reinterpret_cast<AudioDataValue*>(aBuffer), mOutChannels, aFrames);
     614             : 
     615           0 :   if (!strcmp(cubeb_get_backend_id(CubebUtils::GetCubebContext()), "winmm")) {
     616             :     // Don't consume audio data until Start() is called.
     617             :     // Expected only with cubeb winmm backend.
     618           0 :     if (mState == INITIALIZED) {
     619           0 :       NS_WARNING("data callback fires before cubeb_stream_start() is called");
     620           0 :       mAudioClock.UpdateFrameHistory(0, aFrames);
     621           0 :       return writer.WriteZeros(aFrames);
     622             :     }
     623             :   } else {
     624           0 :     MOZ_ASSERT(mState != INITIALIZED);
     625             :   }
     626             : 
     627             :   // NOTE: wasapi (others?) can call us back *after* stop()/Shutdown() (mState == SHUTDOWN)
     628             :   // Bug 996162
     629             : 
     630           0 :   if (mAudioClock.GetInputRate() == mAudioClock.GetOutputRate()) {
     631           0 :     GetUnprocessed(writer);
     632             :   } else {
     633           0 :     GetTimeStretched(writer);
     634             :   }
     635             : 
     636             :   // Always send audible frames first, and silent frames later.
     637             :   // Otherwise it will break the assumption of FrameHistory.
     638           0 :   if (!mDataSource.Ended()) {
     639           0 :     mAudioClock.UpdateFrameHistory(aFrames - writer.Available(), writer.Available());
     640           0 :     if (writer.Available() > 0) {
     641           0 :       LOGW("lost %d frames", writer.Available());
     642           0 :       writer.WriteZeros(writer.Available());
     643             :     }
     644             :   } else {
     645             :     // No more new data in the data source. Don't send silent frames so the
     646             :     // cubeb stream can start draining.
     647           0 :     mAudioClock.UpdateFrameHistory(aFrames - writer.Available(), 0);
     648             :   }
     649             : 
     650           0 :   WriteDumpFile(mDumpFile, this, aFrames, aBuffer);
     651             : 
     652           0 :   return aFrames - writer.Available();
     653             : }
     654             : 
     655             : void
     656           0 : AudioStream::StateCallback(cubeb_state aState)
     657             : {
     658           0 :   MonitorAutoLock mon(mMonitor);
     659           0 :   MOZ_ASSERT(mState != SHUTDOWN, "No state callback after shutdown");
     660           0 :   LOG("StateCallback, mState=%d cubeb_state=%d", mState, aState);
     661           0 :   if (aState == CUBEB_STATE_DRAINED) {
     662           0 :     mState = DRAINED;
     663           0 :     mDataSource.Drained();
     664           0 :   } else if (aState == CUBEB_STATE_ERROR) {
     665           0 :     LOG("StateCallback() state %d cubeb error", mState);
     666           0 :     mState = ERRORED;
     667             :   }
     668           0 : }
     669             : 
     670           0 : AudioClock::AudioClock()
     671             : : mOutRate(0),
     672             :   mInRate(0),
     673             :   mPreservesPitch(true),
     674           0 :   mFrameHistory(new FrameHistory())
     675           0 : {}
     676             : 
     677           0 : void AudioClock::Init(uint32_t aRate)
     678             : {
     679           0 :   mOutRate = aRate;
     680           0 :   mInRate = aRate;
     681           0 : }
     682             : 
     683           0 : void AudioClock::UpdateFrameHistory(uint32_t aServiced, uint32_t aUnderrun)
     684             : {
     685           0 :   mFrameHistory->Append(aServiced, aUnderrun, mOutRate);
     686           0 : }
     687             : 
     688           0 : int64_t AudioClock::GetPositionInFrames(int64_t aFrames) const
     689             : {
     690           0 :   CheckedInt64 v = UsecsToFrames(GetPosition(aFrames), mInRate);
     691           0 :   return v.isValid() ? v.value() : -1;
     692             : }
     693             : 
     694           0 : int64_t AudioClock::GetPosition(int64_t frames) const
     695             : {
     696           0 :   return mFrameHistory->GetPosition(frames);
     697             : }
     698             : 
     699           0 : void AudioClock::SetPlaybackRate(double aPlaybackRate)
     700             : {
     701           0 :   mOutRate = static_cast<uint32_t>(mInRate / aPlaybackRate);
     702           0 : }
     703             : 
     704           0 : double AudioClock::GetPlaybackRate() const
     705             : {
     706           0 :   return static_cast<double>(mInRate) / mOutRate;
     707             : }
     708             : 
     709           0 : void AudioClock::SetPreservesPitch(bool aPreservesPitch)
     710             : {
     711           0 :   mPreservesPitch = aPreservesPitch;
     712           0 : }
     713             : 
     714           0 : bool AudioClock::GetPreservesPitch() const
     715             : {
     716           0 :   return mPreservesPitch;
     717             : }
     718             : 
     719             : } // namespace mozilla

Generated by: LCOV version 1.13