Bug 960873: Part 2: Create AudioCompactor class to minimize allocation slop. r=cpearce
authorBen Kelly <ben@wanderview.com>
Wed, 05 Feb 2014 18:11:25 -0500
changeset 178121 41ddd2fd20313b00e3682a1c921593eb1ace41a0
parent 178120 94dd775ce69818a690ae0563c3d5de5c941b638b
child 178122 703bf8e7c111178b932b57d512437fe637988327
push id5439
push userffxbld
push dateMon, 17 Mar 2014 23:08:15 +0000
treeherdermozilla-aurora@c0befb3c8038 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce
bugs960873
milestone30.0a1
Bug 960873: Part 2: Create AudioCompactor class to minimize allocation slop. r=cpearce Based on original patch written by :gal.
content/media/AudioCompactor.cpp
content/media/AudioCompactor.h
content/media/MediaDecoderReader.cpp
content/media/MediaDecoderReader.h
content/media/MediaDecoderStateMachine.cpp
content/media/MediaDecoderStateMachine.h
content/media/moz.build
new file mode 100644
--- /dev/null
+++ b/content/media/AudioCompactor.cpp
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "AudioCompactor.h"
+#if defined(MOZ_MEMORY)
+# include "mozmemory.h"
+#endif
+
+namespace mozilla {
+
+static size_t
+MallocGoodSize(size_t aSize)
+{
+# if defined(MOZ_MEMORY)
+  return malloc_good_size(aSize);
+# else
+  return aSize;
+# endif
+}
+
+static size_t
+TooMuchSlop(size_t aSize, size_t aAllocSize, size_t aMaxSlop)
+{
+  // If the allocated size is less then our target size, then we
+  // are chunking.  This means it will be completely filled with
+  // zero slop.
+  size_t slop = (aAllocSize > aSize) ? (aAllocSize - aSize) : 0;
+  return slop > aMaxSlop;
+}
+
+uint32_t
+AudioCompactor::GetChunkSamples(uint32_t aFrames, uint32_t aChannels,
+                                size_t aMaxSlop)
+{
+  size_t size = AudioDataSize(aFrames, aChannels);
+  size_t chunkSize = MallocGoodSize(size);
+
+  // Reduce the chunk size until we meet our slop goal or the chunk
+  // approaches an unreasonably small size.
+  while (chunkSize > 64 && TooMuchSlop(size, chunkSize, aMaxSlop)) {
+    chunkSize = MallocGoodSize(chunkSize / 2);
+  }
+
+  // Calculate the number of samples based on expected malloc size
+  // in order to allow as many frames as possible to be packed.
+  return chunkSize / sizeof(AudioDataValue);
+}
+
+uint32_t
+AudioCompactor::NativeCopy::operator()(AudioDataValue *aBuffer,
+                                       uint32_t aSamples)
+{
+  NS_ASSERTION(aBuffer, "cannot copy to null buffer pointer");
+  NS_ASSERTION(aSamples, "cannot copy zero values");
+
+  size_t bufferBytes = aSamples * sizeof(AudioDataValue);
+  size_t maxBytes = std::min(bufferBytes, mSourceBytes - mNextByte);
+  uint32_t frames = maxBytes / BytesPerFrame(mChannels);
+  size_t bytes = frames * BytesPerFrame(mChannels);
+
+  NS_ASSERTION((mNextByte + bytes) <= mSourceBytes,
+               "tried to copy beyond source buffer");
+  NS_ASSERTION(bytes <= bufferBytes, "tried to copy beyond destination buffer");
+
+  memcpy(aBuffer, mSource + mNextByte, bytes);
+
+  mNextByte += bytes;
+  return frames;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/AudioCompactor.h
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#if !defined(AudioCompactor_h)
+#define AudioCompactor_h
+
+#include "MediaQueue.h"
+#include "MediaData.h"
+#include "VideoUtils.h"
+
+namespace mozilla {
+
+class AudioCompactor
+{
+public:
+  AudioCompactor(MediaQueue<AudioData>& aQueue)
+    : mQueue(aQueue)
+  { }
+
+  // Push audio data into the underlying queue with minimal heap allocation
+  // slop.  This method is responsible for allocating AudioDataValue[] buffers.
+  // The caller must provide a functor to copy the data into the buffers.  The
+  // functor must provide the following signature:
+  //
+  //   uint32_t operator()(AudioDataValue *aBuffer, uint32_t aSamples);
+  //
+  // The functor must copy as many complete frames as possible to the provided
+  // buffer given its length (in AudioDataValue elements).  The number of frames
+  // copied must be returned.  This copy functor must support being called
+  // multiple times in order to copy the audio data fully.  The copy functor
+  // must copy full frames as partial frames will be ignored.
+  template<typename CopyFunc>
+  bool Push(int64_t aOffset, int64_t aTime, int32_t aSampleRate,
+            uint32_t aFrames, uint32_t aChannels, CopyFunc aCopyFunc)
+  {
+    // If we are losing more than a reasonable amount to padding, try to chunk
+    // the data.
+    size_t maxSlop = AudioDataSize(aFrames, aChannels) / MAX_SLOP_DIVISOR;
+
+    while (aFrames > 0) {
+      uint32_t samples = GetChunkSamples(aFrames, aChannels, maxSlop);
+      nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[samples]);
+
+      // Copy audio data to buffer using caller-provided functor.
+      uint32_t framesCopied = aCopyFunc(buffer, samples);
+
+      NS_ASSERTION(framesCopied <= aFrames, "functor copied too many frames");
+
+      CheckedInt64 duration = FramesToUsecs(framesCopied, aSampleRate);
+      if (!duration.isValid()) {
+        return false;
+      }
+
+      mQueue.Push(new AudioData(aOffset,
+                                aTime,
+                                duration.value(),
+                                framesCopied,
+                                buffer.forget(),
+                                aChannels));
+
+      // Remove the frames we just pushed into the queue and loop if there is
+      // more to be done.
+      aTime += duration.value();
+      aFrames -= framesCopied;
+
+      // NOTE: No need to update aOffset as its only an approximation anyway.
+    }
+
+    return true;
+  }
+
+  // Copy functor suitable for copying audio samples already in the
+  // AudioDataValue format/layout expected by AudioStream on this platform.
+  class NativeCopy
+  {
+  public:
+    NativeCopy(const uint8_t* aSource, size_t aSourceBytes,
+               uint32_t aChannels)
+      : mSource(aSource)
+      , mSourceBytes(aSourceBytes)
+      , mChannels(aChannels)
+      , mNextByte(0)
+    { }
+
+    uint32_t operator()(AudioDataValue *aBuffer, uint32_t aSamples);
+
+  private:
+    const uint8_t* const mSource;
+    const size_t mSourceBytes;
+    const uint32_t mChannels;
+    size_t mNextByte;
+  };
+
+  // Allow 12.5% slop before chunking kicks in.  Public so that the gtest can
+  // access it.
+  static const size_t MAX_SLOP_DIVISOR = 8;
+
+private:
+  // Compute the number of AudioDataValue samples that will be fit the most
+  // frames while keeping heap allocation slop less than the given threshold.
+  static uint32_t
+  GetChunkSamples(uint32_t aFrames, uint32_t aChannels, size_t aMaxSlop);
+
+  static size_t BytesPerFrame(uint32_t aChannels)
+  {
+    return sizeof(AudioDataValue) * aChannels;
+  }
+
+  static size_t AudioDataSize(uint32_t aFrames, uint32_t aChannels)
+  {
+    return aFrames * BytesPerFrame(aChannels);
+  }
+
+  MediaQueue<AudioData> &mQueue;
+};
+
+} // namespace mozilla
+
+#endif // AudioCompactor_h
--- a/content/media/MediaDecoderReader.cpp
+++ b/content/media/MediaDecoderReader.cpp
@@ -40,17 +40,18 @@ void* MediaDecoderReader::VideoQueueMemo
   if (v->mImage->GetFormat() == ImageFormat::PLANAR_YCBCR) {
     mozilla::layers::PlanarYCbCrImage* vi = static_cast<mozilla::layers::PlanarYCbCrImage*>(v->mImage.get());
     mResult += vi->GetDataSize();
   }
   return nullptr;
 }
 
 MediaDecoderReader::MediaDecoderReader(AbstractMediaDecoder* aDecoder)
-  : mDecoder(aDecoder),
+  : mAudioCompactor(mAudioQueue),
+    mDecoder(aDecoder),
     mIgnoreAudioOutputFormat(false)
 {
   MOZ_COUNT_CTOR(MediaDecoderReader);
 }
 
 MediaDecoderReader::~MediaDecoderReader()
 {
   ResetDecode();
@@ -275,9 +276,8 @@ MediaDecoderReader::GetBuffered(mozilla:
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     durationUs = mDecoder->GetMediaDuration();
   }
   GetEstimatedBufferedTimeRanges(stream, durationUs, aBuffered);
   return NS_OK;
 }
 
 } // namespace mozilla
-
--- a/content/media/MediaDecoderReader.h
+++ b/content/media/MediaDecoderReader.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #if !defined(MediaDecoderReader_h_)
 #define MediaDecoderReader_h_
 
 #include "AbstractMediaDecoder.h"
 #include "MediaInfo.h"
 #include "MediaData.h"
 #include "MediaQueue.h"
+#include "AudioCompactor.h"
 
 namespace mozilla {
 
 namespace dom {
 class TimeRanges;
 }
 
 // Encapsulates the decoding and reading of media data. Reading can only be
@@ -100,16 +101,22 @@ protected:
   // Queue of audio frames. This queue is threadsafe, and is accessed from
   // the audio, decoder, state machine, and main threads.
   MediaQueue<AudioData> mAudioQueue;
 
   // Queue of video frames. This queue is threadsafe, and is accessed from
   // the decoder, state machine, and main threads.
   MediaQueue<VideoData> mVideoQueue;
 
+  // An adapter to the audio queue which first copies data to buffers with
+  // minimal allocation slop and then pushes them to the queue.  This is
+  // useful for decoders working with formats that give awkward numbers of
+  // frames such as mp3.
+  AudioCompactor mAudioCompactor;
+
 public:
   // Populates aBuffered with the time ranges which are buffered. aStartTime
   // must be the presentation time of the first frame in the media, e.g.
   // the media time corresponding to playback time/position 0. This function
   // is called on the main, decode, and state machine threads.
   //
   // This base implementation in MediaDecoderReader estimates the time ranges
   // buffered by interpolating the cached byte ranges with the duration
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -40,16 +40,23 @@ using namespace mozilla::dom;
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gMediaDecoderLog;
 #define DECODER_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg)
 #else
 #define DECODER_LOG(type, msg)
 #endif
 
+// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
+// GetTickCount() and conflicts with MediaDecoderStateMachine::GetCurrentTime
+// implementation.  With unified builds, putting this in headers is not enough.
+#ifdef GetCurrentTime
+#undef GetCurrentTime
+#endif
+
 // Wait this number of seconds when buffering, then leave and play
 // as best as we can if the required amount of data hasn't been
 // retrieved.
 static const uint32_t BUFFERING_WAIT_S = 30;
 
 // If audio queue has less than this many usecs of decoded audio, we won't risk
 // trying to decode the video, we'll skip decoding video up to the next
 // keyframe. We may increase this value for an individual decoder if we
--- a/content/media/MediaDecoderStateMachine.h
+++ b/content/media/MediaDecoderStateMachine.h
@@ -87,16 +87,23 @@ hardware (via AudioStream).
 
 class nsITimer;
 
 namespace mozilla {
 
 class AudioSegment;
 class VideoSegment;
 
+// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
+// GetTickCount() and conflicts with MediaDecoderStateMachine::GetCurrentTime
+// implementation.
+#ifdef GetCurrentTime
+#undef GetCurrentTime
+#endif
+
 /*
   The state machine class. This manages the decoding and seeking in the
   MediaDecoderReader on the decode thread, and A/V sync on the shared
   state machine thread, and controls the audio "push" thread.
 
   All internal state is synchronised via the decoder monitor. State changes
   are either propagated by NotifyAll on the monitor (typically when state
   changes need to be propagated to non-state machine threads) or by scheduling
--- a/content/media/moz.build
+++ b/content/media/moz.build
@@ -53,16 +53,17 @@ TEST_DIRS += [
     'test',
     'gtest',
 ]
 
 EXPORTS += [
     'AbstractMediaDecoder.h',
     'AudioAvailableEventManager.h',
     'AudioChannelFormat.h',
+    'AudioCompactor.h',
     'AudioEventTimeline.h',
     'AudioNodeEngine.h',
     'AudioNodeExternalInputStream.h',
     'AudioNodeStream.h',
     'AudioSampleFormat.h',
     'AudioSegment.h',
     'AudioStream.h',
     'BufferDecoder.h',
@@ -110,16 +111,17 @@ EXPORTS.mozilla.dom += [
     'TextTrackRegionList.h',
     'VideoPlaybackQuality.h',
     'VideoStreamTrack.h',
 ]
 
 UNIFIED_SOURCES += [
     'AudioAvailableEventManager.cpp',
     'AudioChannelFormat.cpp',
+    'AudioCompactor.cpp',
     'AudioNodeEngine.cpp',
     'AudioNodeExternalInputStream.cpp',
     'AudioNodeStream.cpp',
     'AudioSegment.cpp',
     'AudioStream.cpp',
     'AudioStreamTrack.cpp',
     'BufferDecoder.cpp',
     'DOMMediaStream.cpp',