Bug 1552530 - Make the wav dumper in AudioStream.cpp reusable. r=jya
authorPaul Adenot <paul@paul.cx>
Tue, 21 May 2019 10:10:04 +0000
changeset 474708 6d2b52f066bf543dbbcbc3299de5147332392d82
parent 474707 c4045738b0cdb76f1ff2c492c390751147bce62c
child 474709 561a328e09ea3fc0e5fe93cf0c9d803b4695186f
push id113168
push userrmaries@mozilla.com
push dateTue, 21 May 2019 16:39:23 +0000
treeherdermozilla-inbound@3c0f78074b72 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjya
bugs1552530
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1552530 - Make the wav dumper in AudioStream.cpp reusable. r=jya Differential Revision: https://phabricator.services.mozilla.com/D31648
dom/media/AudioStream.cpp
dom/media/AudioStream.h
dom/media/WavDumper.h
dom/media/moz.build
--- a/dom/media/AudioStream.cpp
+++ b/dom/media/AudioStream.cpp
@@ -124,34 +124,30 @@ class FrameHistory {
   double mBasePosition;
 };
 
 AudioStream::AudioStream(DataSource& aSource)
     : mMonitor("AudioStream"),
       mChannels(0),
       mOutChannels(0),
       mTimeStretcher(nullptr),
-      mDumpFile(nullptr),
       mState(INITIALIZED),
       mDataSource(aSource),
       mPrefillQuirk(false) {
 #if defined(XP_WIN)
   if (XRE_IsContentProcess()) {
     audio::AudioNotificationReceiver::Register(this);
   }
 #endif
 }
 
 AudioStream::~AudioStream() {
   LOG("deleted, state %d", mState);
   MOZ_ASSERT(mState == SHUTDOWN && !mCubebStream,
              "Should've called Shutdown() before deleting an AudioStream");
-  if (mDumpFile) {
-    fclose(mDumpFile);
-  }
   if (mTimeStretcher) {
     soundtouch::destroySoundTouchObj(mTimeStretcher);
   }
 #if defined(XP_WIN)
   if (XRE_IsContentProcess()) {
     audio::AudioNotificationReceiver::Unregister(this);
   }
 #endif
@@ -230,89 +226,16 @@ nsresult AudioStream::SetPreservesPitch(
     mTimeStretcher->setRate(mAudioClock.GetPlaybackRate());
   }
 
   mAudioClock.SetPreservesPitch(aPreservesPitch);
 
   return NS_OK;
 }
 
-static void SetUint16LE(uint8_t* aDest, uint16_t aValue) {
-  aDest[0] = aValue & 0xFF;
-  aDest[1] = aValue >> 8;
-}
-
-static void SetUint32LE(uint8_t* aDest, uint32_t aValue) {
-  SetUint16LE(aDest, aValue & 0xFFFF);
-  SetUint16LE(aDest + 2, aValue >> 16);
-}
-
-static FILE* OpenDumpFile(uint32_t aChannels, uint32_t aRate) {
-  /**
-   * When MOZ_DUMP_AUDIO is set in the environment (to anything),
-   * we'll drop a series of files in the current working directory named
-   * dumped-audio-<nnn>.wav, one per AudioStream created, containing
-   * the audio for the stream including any skips due to underruns.
-   */
-  static Atomic<int> gDumpedAudioCount(0);
-
-  if (!getenv("MOZ_DUMP_AUDIO")) return nullptr;
-  char buf[100];
-  SprintfLiteral(buf, "dumped-audio-%d.wav", ++gDumpedAudioCount);
-  FILE* f = fopen(buf, "wb");
-  if (!f) return nullptr;
-
-  uint8_t header[] = {
-      // RIFF header
-      0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45,
-      // fmt chunk. We always write 16-bit samples.
-      0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFF, 0xFF,
-      0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x10, 0x00,
-      // data chunk
-      0x64, 0x61, 0x74, 0x61, 0xFE, 0xFF, 0xFF, 0x7F};
-  static const int CHANNEL_OFFSET = 22;
-  static const int SAMPLE_RATE_OFFSET = 24;
-  static const int BLOCK_ALIGN_OFFSET = 32;
-  SetUint16LE(header + CHANNEL_OFFSET, aChannels);
-  SetUint32LE(header + SAMPLE_RATE_OFFSET, aRate);
-  SetUint16LE(header + BLOCK_ALIGN_OFFSET, aChannels * 2);
-  Unused << fwrite(header, sizeof(header), 1, f);
-
-  return f;
-}
-
-template <typename T>
-typename EnableIf<IsSame<T, int16_t>::value, void>::Type WriteDumpFileHelper(
-    T* aInput, size_t aSamples, FILE* aFile) {
-  Unused << fwrite(aInput, sizeof(T), aSamples, aFile);
-}
-
-template <typename T>
-typename EnableIf<IsSame<T, float>::value, void>::Type WriteDumpFileHelper(
-    T* aInput, size_t aSamples, FILE* aFile) {
-  AutoTArray<uint8_t, 1024 * 2> buf;
-  buf.SetLength(aSamples * 2);
-  uint8_t* output = buf.Elements();
-  for (uint32_t i = 0; i < aSamples; ++i) {
-    SetUint16LE(output + i * 2, int16_t(aInput[i] * 32767.0f));
-  }
-  Unused << fwrite(output, 2, aSamples, aFile);
-  fflush(aFile);
-}
-
-static void WriteDumpFile(FILE* aDumpFile, AudioStream* aStream,
-                          uint32_t aFrames, void* aBuffer) {
-  if (!aDumpFile) return;
-
-  uint32_t samples = aStream->GetOutChannels() * aFrames;
-
-  using SampleT = AudioSampleTraits<AUDIO_OUTPUT_FORMAT>::Type;
-  WriteDumpFileHelper(reinterpret_cast<SampleT*>(aBuffer), samples, aDumpFile);
-}
-
 template <AudioSampleFormat N>
 struct ToCubebFormat {
   static const cubeb_sample_format value = CUBEB_SAMPLE_FLOAT32NE;
 };
 
 template <>
 struct ToCubebFormat<AUDIO_FORMAT_S16> {
   static const cubeb_sample_format value = CUBEB_SAMPLE_S16NE;
@@ -328,27 +251,28 @@ nsresult AudioStream::Init(uint32_t aNum
                            AudioConfig::ChannelLayout::ChannelMap aChannelMap,
                            uint32_t aRate, AudioDeviceInfo* aSinkInfo) {
   auto startTime = TimeStamp::Now();
 
   LOG("%s channels: %d, rate: %d", __FUNCTION__, aNumChannels, aRate);
   mChannels = aNumChannels;
   mOutChannels = aNumChannels;
 
-  mDumpFile = OpenDumpFile(aNumChannels, aRate);
-
   mSinkInfo = aSinkInfo;
 
   cubeb_stream_params params;
   params.rate = aRate;
   params.channels = mOutChannels;
   params.layout = static_cast<uint32_t>(aChannelMap);
   params.format = ToCubebFormat<AUDIO_OUTPUT_FORMAT>::value;
   params.prefs = CubebUtils::GetDefaultStreamPrefs();
 
+  // This is noop if MOZ_DUMP_AUDIO is not set.
+  mDumpFile.Open("AudioStream", mOutChannels, aRate);
+
   mAudioClock.Init(aRate);
 
   cubeb* cubebContext = CubebUtils::GetCubebContext();
   if (!cubebContext) {
     LOGE("Can't get cubeb context!");
     CubebUtils::ReportCubebStreamInitFailure(true);
     return NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR;
   }
@@ -663,17 +587,18 @@ long AudioStream::DataCallback(void* aBu
       writer.WriteZeros(writer.Available());
     }
   } else {
     // No more new data in the data source. Don't send silent frames so the
     // cubeb stream can start draining.
     mAudioClock.UpdateFrameHistory(aFrames - writer.Available(), 0);
   }
 
-  WriteDumpFile(mDumpFile, this, aFrames, aBuffer);
+  mDumpFile.Write(static_cast<const AudioDataValue*>(aBuffer),
+                  aFrames * mOutChannels);
 
   return aFrames - writer.Available();
 }
 
 void AudioStream::StateCallback(cubeb_state aState) {
   MonitorAutoLock mon(mMonitor);
   MOZ_ASSERT(mState != SHUTDOWN, "No state callback after shutdown");
   LOG("StateCallback, mState=%d cubeb_state=%d", mState, aState);
--- a/dom/media/AudioStream.h
+++ b/dom/media/AudioStream.h
@@ -12,16 +12,17 @@
 #  include "mozilla/Monitor.h"
 #  include "mozilla/RefPtr.h"
 #  include "mozilla/TimeStamp.h"
 #  include "mozilla/UniquePtr.h"
 #  include "nsAutoPtr.h"
 #  include "nsCOMPtr.h"
 #  include "nsThreadUtils.h"
 #  include "soundtouch/SoundTouchFactory.h"
+#  include "WavDumper.h"
 
 #  if defined(XP_WIN)
 #    include "mozilla/audio/AudioNotificationReceiver.h"
 #  endif
 
 namespace mozilla {
 
 struct CubebDestroyPolicy {
@@ -303,18 +304,17 @@ class AudioStream final
   // The monitor is held to protect all access to member variables.
   Monitor mMonitor;
 
   uint32_t mChannels;
   uint32_t mOutChannels;
   AudioClock mAudioClock;
   soundtouch::SoundTouch* mTimeStretcher;
 
-  // Output file for dumping audio
-  FILE* mDumpFile;
+  WavDumper mDumpFile;
 
   // Owning reference to a cubeb_stream.
   UniquePtr<cubeb_stream, CubebDestroyPolicy> mCubebStream;
 
   enum StreamState {
     INITIALIZED,  // Initialized, playback has not begun.
     STARTED,      // cubeb started.
     STOPPED,      // Stopped by a call to Pause().
new file mode 100644
--- /dev/null
+++ b/dom/media/WavDumper.h
@@ -0,0 +1,117 @@
+#if !defined(WavDumper_h_)
+#  define WavDumper_h_
+#  include <stdio.h>
+#  include <stdint.h>
+#  include <nsTArray.h>
+#  include <mozilla/Unused.h>
+#  include <mozilla/Atomics.h>
+#  include <mozilla/DebugOnly.h>
+#  include <ByteWriter.h>
+
+/**
+ * If MOZ_DUMP_AUDIO is set, this dumps a file to disk containing the output of
+ * an audio stream, in 16bits integers.
+ *
+ * The sandbox needs to be disabled for this to work.
+ */
+class WavDumper {
+ public:
+  WavDumper() = default;
+  ~WavDumper() {
+    if (mFile) {
+      fclose(mFile);
+    }
+  }
+  void Open(const char* aBaseName, uint32_t aChannels, uint32_t aRate) {
+    using namespace mozilla;
+
+    if (!getenv("MOZ_DUMP_AUDIO")) {
+      return;
+    }
+
+    static mozilla::Atomic<int> sDumpedAudioCount(0);
+
+    char buf[100];
+    SprintfLiteral(buf, "%s-%d.wav", aBaseName, ++sDumpedAudioCount);
+    mFile = fopen(buf, "wb");
+    if (!mFile) {
+      NS_WARNING("Could not open file to DUMP a wav. Is sandboxing disabled?");
+      return;
+    }
+    const uint8_t riffHeader[] = {
+        // RIFF header
+        0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45,
+        // fmt chunk. We always write 16-bit samples.
+        0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0xFF, 0xFF,
+        0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x10, 0x00,
+        // data chunk
+        0x64, 0x61, 0x74, 0x61, 0xFE, 0xFF, 0xFF, 0x7F};
+    AutoTArray<uint8_t, sizeof(riffHeader)> header;
+    ByteWriter<LittleEndian> writer(header);
+    static const int CHANNEL_OFFSET = 22;
+    static const int SAMPLE_RATE_OFFSET = 24;
+    static const int BLOCK_ALIGN_OFFSET = 32;
+
+    DebugOnly<bool> rv;
+    // Then number of bytes written in each iteration.
+    uint32_t written = 0;
+    for (size_t i = 0; i != sizeof(riffHeader);) {
+      switch (i) {
+        case CHANNEL_OFFSET:
+          rv = writer.WriteU16(aChannels);
+          written = 2;
+          MOZ_ASSERT(rv);
+          break;
+        case SAMPLE_RATE_OFFSET:
+          rv = writer.WriteU32(aRate);
+          written = 4;
+          MOZ_ASSERT(rv);
+          break;
+        case BLOCK_ALIGN_OFFSET:
+          rv = writer.WriteU16(aChannels * 2);
+          written = 2;
+          MOZ_ASSERT(rv);
+          break;
+        default:
+          // copy from the riffHeader struct above
+          rv = writer.WriteU8(riffHeader[i]);
+          written = 1;
+          MOZ_ASSERT(rv);
+          break;
+      }
+      i += written;
+    }
+    Unused << fwrite(header.Elements(), header.Length(), 1, mFile);
+  }
+
+  template <typename T>
+  void Write(const T* aBuffer, uint32_t aSamples) {
+    if (!mFile) {
+      return;
+    }
+    WriteDumpFileHelper(aBuffer, aSamples);
+  }
+
+ private:
+  void WriteDumpFileHelper(const int16_t* aInput, size_t aSamples) {
+    mozilla::Unused << fwrite(aInput, sizeof(int16_t), aSamples, mFile);
+    fflush(mFile);
+  }
+
+  void WriteDumpFileHelper(const float* aInput, size_t aSamples) {
+    using namespace mozilla;
+
+    AutoTArray<uint8_t, 1024 * 2> buf;
+    ByteWriter<mozilla::LittleEndian> writer(buf);
+    for (uint32_t i = 0; i < aSamples; ++i) {
+      DebugOnly<bool> rv = writer.WriteU16(int16_t(aInput[i] * 32767.0f));
+      MOZ_ASSERT(rv);
+    }
+    mozilla::Unused << fwrite(buf.Elements(), buf.Length(), 1, mFile);
+    fflush(mFile);
+  }
+
+  FILE* mFile = nullptr;
+};
+
+#endif  // WavDumper_h_
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -161,16 +161,17 @@ EXPORTS += [
     'Tracing.h',
     'TrackID.h',
     'TrackUnionStream.h',
     'VideoFrameContainer.h',
     'VideoLimits.h',
     'VideoSegment.h',
     'VideoUtils.h',
     'VorbisUtils.h',
+    'WavDumper.h',
     'XiphExtradata.h',
 ]
 
 EXPORTS.mozilla += [
     'MediaManager.h',
 ]
 
 EXPORTS.mozilla.media.webrtc += [